<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>권퓨터: Kwonputer</title>
    <link>https://kwonputer.tistory.com/</link>
    <description>만드는 걸 좋아하는 개발자의 기록. 코드든 글이든, 일단 만들어 봅니다.</description>
    <language>ko</language>
    <pubDate>Mon, 15 Jun 2026 22:36:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>권퓨터</managingEditor>
    <image>
      <title>권퓨터: Kwonputer</title>
      <url>https://tistory1.daumcdn.net/tistory/4158688/attach/5fa07255e2454a1db64bd374c24af521</url>
      <link>https://kwonputer.tistory.com</link>
    </image>
    <item>
      <title>10개 다국어 블로그 운영 D+87 리포트 &amp;mdash; 방문자 1,763명, 검색노출 1,629회 달성</title>
      <link>https://kwonputer.tistory.com/659</link>
      <description>&lt;div style=&quot;text-align: center; padding: 40px 20px 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #6b7280; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;D+87 &amp;middot; 2026년 04월 29일 기준&lt;/p&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;D+87일, AI 블로그 실험의 첫 3개월 결과&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 10px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 직접 작성한 글만으로 10개국 블로그를 운영하는 무모한(?) 실험이 시작된 지 87일이 지났습니다. 한국부터 일본, 독일, 프랑스까지 각기 다른 언어와 문화권에서 과연 AI 콘텐츠가 얼마나 통할까 싶어 시작한 이 프로젝트도 이제 첫 분기를 넘어섰네요.&lt;/p&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;지난 87일간 10개 블로그를 합쳐서 총 1,766명이 방문했고, 2,240번의 페이지뷰를 기록했습니다. 검색엔진에는 1,708회 노출됐고요. 솔직히 대박은 아니지만, 완전 망한 것도 아닌 애매한 성과랄까요?&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;h2 style=&quot;font-size: 22px; color: #1e293b; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size26&quot;&gt;1월의 블로그 성과 한눈에&lt;/h2&gt;
&lt;div style=&quot;height: 5px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; border: 1px solid #e2e8f0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;background-color: #475569;&quot;&gt;
&lt;th style=&quot;padding: 12px; text-align: left; color: #ffffff; font-size: 14px;&quot;&gt;블로그&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: left; color: #ffffff; font-size: 14px;&quot;&gt;언어&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: right; color: #ffffff; font-size: 14px;&quot;&gt;방문자&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: right; color: #ffffff; font-size: 14px;&quot;&gt;페이지뷰&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: right; color: #ffffff; font-size: 14px;&quot;&gt;검색 노출&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com&quot;&gt;Kwonputer&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;한국어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;168&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;384&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;122&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com&quot;&gt;Kwonglish&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;영어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;216&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;285&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com&quot;&gt;Kwonnen&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;독일어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;139&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;188&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;147&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com&quot;&gt;Kwonteki&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;일본어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;154&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;181&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com&quot;&gt;Kwontenu&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;프랑스어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;132&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;149&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com&quot;&gt;Kwonsejo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;스페인어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;203&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;246&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;213&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com&quot;&gt;Kwontudo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;포르투갈어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;327&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;346&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;206&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnis.com&quot;&gt;Kwonnis&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;네덜란드어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;133&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;140&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontento.com&quot;&gt;Kwontento&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;이탈리아어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;144&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;150&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;138&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com&quot;&gt;Kwontrol&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;터키어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;147&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;150&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #1e293b;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; font-weight: bold;&quot; colspan=&quot;2&quot;&gt;합계&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; text-align: right; font-weight: bold;&quot;&gt;1,763&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; text-align: right; font-weight: bold;&quot;&gt;2,219&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; text-align: right; font-weight: bold;&quot;&gt;1,629&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 13px; color: #9ca3af; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;* 방문자&amp;middot;페이지뷰: Google Analytics / 검색 노출: Google Search Console&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;h2 style=&quot;font-size: 22px; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size26&quot;&gt;각 블로그를 좀 더 자세히 보면&lt;/h2&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. Kwonputer &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 한국어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com&quot;&gt;https://kwonputer.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/ko-homepage.png&quot; alt=&quot;Kwonputer 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;166&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;384&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;25.2%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;132&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;5&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;aws프리티어&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;게임리뷰&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;로그라이크 추천&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;스토리북 디자인 시스템&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;유튜브 프리미엄 우회 클리앙&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com/docker-docker-입문-완벽-가이드-개발-환경-세팅부터-배포까/&quot;&gt;[Docker] Docker 입문 완벽 가이드 &amp;mdash; 개발 환경 세팅부터 배포까지 - 권...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com/git-github-입문-완벽-가이드-개발자-필수-도구-2/&quot;&gt;Git &amp;amp; GitHub 입문 완벽 가이드 &amp;mdash; 개발자 필수 도구 - 권퓨터: Kwonp...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com/2026년-프론트엔드-트렌드-총정리-react-19-next-js-1516-turbopack-react-compiler-2/&quot;&gt;2026년 프론트엔드 트렌드 총정리 &amp;mdash; React 19, Next.js 15~16, ...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;166명이 찾아와 Docker와 Git 가이드가 인기몰이, AWS 프리티어 검색도 꾸준해요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. Kwonglish &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 영어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com&quot;&gt;https://kwonglish.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/en-homepage.png&quot; alt=&quot;Kwonglish 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;219&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;293&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;16.2%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;408&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;best hidden gem restaurants nyc&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;dell xps vs macbook pro 2025 2026 comparison which is better&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;dell xps vs macbook pro comparison 2025 or 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;dell xps vs macbook pro comparison 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;ebbinghaus forgetting curve textbook new edition 2026&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com/docker-kubernetes-migration-guide-2026/&quot;&gt;[DevOps] Complete Docker to Kubernetes Migratio...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com/spring-2026-anime-season-preview/&quot;&gt;[Anime] Spring 2026 Anime Season Preview: Must-...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com/productivity-the-ultimate-developer-workflow-setup-2026-vs-code-extensions-terminal-tools-and-automation-scripts/&quot;&gt;[Productivity] The Ultimate Developer Workflow ...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;219명 방문자로 최고 성과! NYC 맛집과 맥북 비교글이 검색에서 강세네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. Kwonnen &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 독일어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com&quot;&gt;https://kwonnen.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/de-homepage.png&quot; alt=&quot;Kwonnen 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;137&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;188&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;21.7%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;163&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;progressive web app 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;postgrest&quot;&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;andt design&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;ant design vs material ui&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;cafe laptop berlin&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com/best-psychological-anime-2026-recommendations/&quot;&gt;[Anime] Die besten psychologischen Anime 2026: ...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com/python-vs-r-machine-learning-2026/&quot;&gt;[Machine Learning] Python vs R f&amp;uuml;r Data Science...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com/die-7-besten-geheimtipp-restaurants-in-berlin-lokale-favoriten-die-man-probieren-muss-2/&quot;&gt;Die 7 besten Geheimtipp-Restaurants in Berlin &amp;mdash;...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;137명 방문으로 안정적 성장, 심리 애니메이션과 Python vs R 포스트가 주목받고 있어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;4. Kwonteki &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 일본어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com&quot;&gt;https://kwonteki.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/ja-homepage.png&quot; alt=&quot;Kwonteki 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;154&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;182&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;18.2%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;136&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;2&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;next.js 15 app router documentation&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;next.js 15 app router official docs&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;next.js 15 release app router&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;pkm ツール 比較 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;パズル プログラミング&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com/developer-headphones-comparison-2026/&quot;&gt;[テック・トレンド] 開発者の集中力を最大化！2026年版最新ノイズキャンセリングヘッドホン徹...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com/東京で本当に美味しい隠れ家レストラン7選-地/&quot;&gt;東京で本当に美味しい隠れ家レストラン7選 &amp;mdash; 地元民が通う名店ガイド - Kwonteki&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com/pkm-tools-comparison-developers-2026/&quot;&gt;[テック・トレンド] 開発者のための知識管理術 2026: セカンドブレインを構築する最新PK...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;154명이 방문해 Next.js 15 관심 집중, 헤드폰 리뷰와 도쿄 맛집 가이드가 인기예요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;5. Kwontenu &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 프랑스어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com&quot;&gt;https://kwontenu.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/fr-homepage.png&quot; alt=&quot;Kwontenu 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;131&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;149&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;8.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;86&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;(inurl:comment) collisions&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;framework laptop&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;framework+laptop&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;laptop framework&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;pc portable d&amp;eacute;veloppeur&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com/flutter-comment-optimiser-les-performances-de-votre-application-mobile-en-2026/&quot;&gt;[Flutter] Comment optimiser les performances de...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com/disclaimer/&quot;&gt;Mentions L&amp;eacute;gales &amp;amp; Avertissement - Kwontenu&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;131명 방문으로 꾸준한 흐름, Flutter 성능 최적화 글이 개발자들에게 어필하고 있네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;6. Kwonsejo &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 스페인어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com&quot;&gt;https://kwonsejo.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/es-homepage.png&quot; alt=&quot;Kwonsejo 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;204&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;249&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;11.0%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;231&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;4&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;data version control&quot; -site:reddit.com -site:twitter.com -site:x.com -site:wykop.pl -site:tripadvisor.com -site:youtube.com -site:yelp.com -site:booking.com -site:facebook.com -site:instagram.com -site:tiktok.com&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;anime&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;estrategia de monetizaci&amp;oacute;n de api&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;helldivers 2 builds 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;mejores animes para principiantes 2026&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com/gaming-helldivers-2-guia-de-estratagemas-y-builds-para-la-victoria-en-2026/&quot;&gt;[Gaming] Helldivers 2: Gu&amp;iacute;a de Estratagemas y B...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com/crear-chatbot-langchain-openai-gpt4o-tutorial/&quot;&gt;[IA &amp;amp; ML] C&amp;oacute;mo crear tu primer chatbot con Lang...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com/anime-los-mejores-animes-para-principiantes-en-2026-guia-esencial-para-nuevos-fans/&quot;&gt;[Anime] Los Mejores Animes para Principiantes e...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;204명으로 좋은 성과! Helldivers 2 공략과 AI 챗봇 튜토리얼이 독자들 시선 사로잡았어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;7. Kwontudo &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 포르투갈어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com&quot;&gt;https://kwontudo.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/pt-homepage.png&quot; alt=&quot;Kwontudo 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;331&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;355&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;4.7%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;213&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;mercado bitcoin&quot;&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;mlkittextrecognitionlatin&quot;&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;mlkittextrecognitionlatin&quot; swift&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;blockchain e web3 not&amp;iacute;cias tecnologia brasil 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;shakti&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com/games-os-10-melhores-jogos-indie-do-nintendo-switch-em-2026-joias-escondidas-que-voce-precisa-jogar/&quot;&gt;[Games] Os 10 Melhores Jogos Indie do Nintendo ...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com/ps5-exclusive-games-2026/&quot;&gt;[Games] Os Melhores Jogos Exclusivos de PlaySta...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com/web3-blockchain-impact-2026/&quot;&gt;[Not&amp;iacute;cias Tech] Web3 em 2026: Al&amp;eacute;m do Hype, Ond...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;331명으로 전체 1위 달성! Nintendo Switch 인디게임 리뷰가 브라질 게이머들 마음 저격했네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;8. Kwonnis &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 네덜란드어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnis.com&quot;&gt;https://kwonnis.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/nl-homepage.png&quot; alt=&quot;Kwonnis 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;133&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;140&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;6.5%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;72&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;apple m4 vs intel core ultra benchmarks&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;apple m4 vs intel core ultra performance comparison&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;overdrachtsbelasting bij verkoop huis&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;overdrachtsbelasting verkoop huis&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnis.com/apple-m4-vs-intel-core-ultra-benchmark-2026/&quot;&gt;[Hardware] Apple M4 vs Intel Core Ultra: Comple...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;133명 방문으로 차근차근, Apple M4 벤치마크 글이 개발자들 사이에서 화제가 되고 있어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;9. Kwontento &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 이탈리아어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontento.com&quot;&gt;https://kwontento.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/it-homepage.png&quot; alt=&quot;Kwontento 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;144&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;150&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;7.4%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;141&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;come fare un caff&amp;egrave; perfetto&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;cosa ti serve per fare il caff&amp;egrave; a casa&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;doppie imposizioni&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;fare un buon caff&amp;egrave; espresso in casa&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;giochi free to play pc&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;144명이 찾아왔지만 인기글이 아직 없어요. 완벽한 에스프레소 검색이 이탈리아다워요!&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;10. Kwontrol &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 터키어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com&quot;&gt;https://kwontrol.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-29/tr-homepage.png&quot; alt=&quot;Kwontrol 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;147&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;150&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;7.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;126&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;2026 &amp;ccedil;ıkacak oyunlar&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;en iyi pastaneler&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;istanbul tatlıcıları&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&amp;uuml;nl&amp;uuml; pastaneler&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com/saglik-yasam-gelistiriciler-icin-goz-sagligi-rehberi-ekran-yorgunlugunu-azaltma-ve-koruma-yontemleri-2026/&quot;&gt;[Sağlık &amp;amp; Yaşam] Geliştiriciler İ&amp;ccedil;in G&amp;ouml;z Sağlığ...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com/anime-2026-ilkbahar-sezonu-anime-rehberi-yeni-baslayanlar-ve-devam-eden-seriler/&quot;&gt;2026 bahar sezonu anime rehberi: bu sezon ka&amp;ccedil;ır...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;147명 방문으로 안정세, 개발자 눈건강 가이드와 2026 게임 전망이 터키 독자들 관심끌었네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;h2 style=&quot;font-size: 22px; color: #1e293b; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size26&quot;&gt;87일째, 1,700명과의 만남&lt;/h2&gt;
&lt;div style=&quot;height: 5px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;87일 동안 1,766명의 방문자와 만났고, 검색에서 1,708번 노출되었네요. Kwontudo가 331명으로 가장 많은 사랑을 받았고, Kwonglish는 408번 검색 노출로 꾸준한 관심을 증명했어요.&lt;/p&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;365일까지 아직 278일 남았는데, 이 속도라면 정말 기대되는 여정이 될 것 같아요. 여러분의 관심과 응원이 이 실험의 가장 큰 동력이니까요!&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 15px 20px; background-color: #f8f9fa; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 12px; color: #9ca3af; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;2026년 04월 29일 기준 데이터 &amp;middot; Google Analytics + Search Console&lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/마케팅 &amp;middot; 수익화</category>
      <category>AI블로그</category>
      <category>GA4</category>
      <category>검색노출</category>
      <category>다국어블로그</category>
      <category>블로그수익화</category>
      <category>블로그운영</category>
      <category>운영리포트</category>
      <category>워드프레스</category>
      <category>콘텐츠자동화</category>
      <category>티스토리</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/659</guid>
      <comments>https://kwonputer.tistory.com/659#entry659comment</comments>
      <pubDate>Wed, 29 Apr 2026 14:37:22 +0900</pubDate>
    </item>
    <item>
      <title>웹 랜딩페이지에서 앱 다운로드 전환율을 높인 콘텐츠 잠금 전략 &amp;mdash; GA4 퍼널 분석부터 구현까지</title>
      <link>https://kwonputer.tistory.com/658</link>
      <description>&lt;div style=&quot;background-color: #e64a19; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #ffccbc; font-size: 13px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;요약&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 24px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;콘텐츠 잠금으로 웹-투-앱 전환율 높이기&lt;/p&gt;
&lt;p style=&quot;color: #fbe9e7; font-size: 16px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;웹 랜딩페이지에서 사용자가 콘텐츠를 다 보고 이탈하는 문제를, 일부 콘텐츠를 잠금 처리하고 앱 다운로드를 유도하는 방식으로 해결한 실제 경험을 공유합니다.&lt;/p&gt;
&lt;p style=&quot;color: #fbe9e7; font-size: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 키워드: &lt;b&gt;콘텐츠 잠금&lt;/b&gt;, &lt;b&gt;GA4 퍼널&lt;/b&gt;, &lt;b&gt;앱 다운로드 전환&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px;&quot;&gt;
&lt;h2 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size26&quot;&gt;이 글의 순서&lt;/h2&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;1. 문제 발견 &amp;mdash; GA4 퍼널로 이탈 지점 확인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;2. 해결 전략 &amp;mdash; 콘텐츠 잠금 설계&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;3. 구현 &amp;mdash; 카드 잠금, 다운로드 모달, CTA 바&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;4. GA 이벤트 설계 &amp;mdash; 터치포인트별 추적&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;5. 결과와 교훈&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;6. 마무리&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===== 섹션 1: 문제 발견 ===== --&gt;
&lt;p style=&quot;color: #e64a19; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;문제_발견&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;GA4 퍼널로 이탈 지점 확인&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;저는 사주/점성술 분석 앱 &lt;b&gt;Fortie&lt;/b&gt;를 운영하고 있습니다. 앱 외에도 웹에서 접근 가능한 공개 분석 페이지(&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;/birth-chart&lt;/code&gt;)를 운영하는데, SEO와 공유 링크를 통해 상당한 트래픽이 유입되고 있었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;문제는 사용자들이 생년월일을 입력하고 사주(Four Pillars) 분석과 스텔라(서양 점성술) 분석 결과를 &lt;b&gt;웹에서 모두 확인한 뒤, 앱을 다운로드하지 않고 그냥 이탈&lt;/b&gt;한다는 것이었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;이탈 지점을 정확히 파악하기 위해 GA4에서 다음과 같은 퍼널을 설정했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e64a19; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;GA4 전환 퍼널&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1&lt;/b&gt; &amp;mdash; analysis_view: 분석 페이지 진입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 2&lt;/b&gt; &amp;mdash; analysis_submit: 생년월일 입력 후 분석 요청&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3&lt;/b&gt; &amp;mdash; analysis_result: 결과 화면 도달&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 4&lt;/b&gt; &amp;mdash; cta_click: CTA 버튼 클릭 (로그인 또는 다운로드)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff0f0; border-left: 4px solid #e03131; border-radius: 0 12px 12px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #e03131; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;analysis_result &amp;rarr; cta_click&lt;/b&gt; 단계에서 대부분의 사용자가 이탈하고 있었습니다. 결과는 보지만 다음 행동으로 연결되지 않는 것이 문제였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;원인은 명확했습니다. 사용자가 원하는 정보(분석 결과)를 웹에서 이미 &lt;b&gt;모두&lt;/b&gt; 얻었기 때문에, 앱을 다운로드할 동기가 사라진 것입니다. 12개 인사이트 카드(에너지, 균형, 조화, 별, 내면, 인생흐름 / 정체성, 별자리맵, 대화, 나침반, 무대, 운세)를 전부 클릭하면 상세 내용까지 다 볼 수 있었으니까요.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===== 섹션 2: 해결 전략 ===== --&gt;
&lt;p style=&quot;color: #e64a19; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;해결_전략&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;콘텐츠 잠금 설계&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 간단합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8e1; border-left: 4px solid #ffd600; border-radius: 0 12px 12px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #f57f17; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 전략&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;무료로 맛보기를 제공하되, 전체 분석은 앱에서만 볼 수 있게 한다.&quot;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 웹에서 모든 콘텐츠에 자유롭게 접근할 수 있었습니다. 이것을 다음과 같은 3단계 구조로 변경했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;display: inline-block; background-color: #e64a19; color: #fff; font-size: 14px; font-weight: bold; padding: 4px 12px; border-radius: 50%;&quot;&gt;1&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;요약 카드 &amp;mdash; 제목과 서브타이틀은 보여줌&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;인사이트 카드의 제목(예: &quot;에너지 균형&quot;)과 한 줄 설명은 그대로 노출합니다. 사용자의 흥미를 유발하되, 상세 내용은 숨깁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;display: inline-block; background-color: #e64a19; color: #fff; font-size: 14px; font-weight: bold; padding: 4px 12px; border-radius: 50%;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;카드 잠금 &amp;mdash; 탭하면 앱 다운로드 유도&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;12개 인사이트 카드를 모두 잠금 처리합니다. 어두운 오버레이 + 상단에 &quot;앱에서 자세히 보기&quot; CTA 배너를 표시하고, 탭하면 기존 상세 화면 대신 앱 다운로드 모달이 열립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;display: inline-block; background-color: #e64a19; color: #fff; font-size: 14px; font-weight: bold; padding: 4px 12px; border-radius: 50%;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;프리미엄 콘텐츠도 다운로드 유도로 전환&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;기존에 로그인 유도하던 블러 카드(맞춤 해석, 오늘의 조언, 크라우드 통계)도 로그인 페이지가 아닌 앱 다운로드 모달로 연결합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===== 섹션 3: 구현 ===== --&gt;
&lt;p style=&quot;color: #e64a19; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;구현_상세&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;카드 잠금, 다운로드 모달, CTA 바&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;1. 인사이트 카드 잠금 처리&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;기존 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;InsightCard&lt;/code&gt; 컴포넌트에 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;lockVariant=&quot;download&quot;&lt;/code&gt; 라는 새로운 잠금 모드를 추가했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e64a19; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;download 잠금 모드 특징&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어두운 오버레이&lt;/b&gt; &amp;mdash; 카드 전체에 반투명 검은색 오버레이&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상단 CTA 배너&lt;/b&gt; &amp;mdash; 자물쇠 아이콘 + &quot;앱에서 {카드명} 자세히 보기&quot; 문구&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카드별 동적 문구&lt;/b&gt; &amp;mdash; 백엔드의 metaphor.label을 활용한 템플릿&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제목/서브타이틀은 유지&lt;/b&gt; &amp;mdash; 흥미 유발을 위해 카드 하단 텍스트는 그대로 노출&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;기존 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;full&lt;/code&gt; 잠금 모드는 짙은 블러로 카드를 완전히 가렸지만, &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;download&lt;/code&gt; 모드는 카드의 그라데이션 배경과 제목이 살짝 보이도록 설계했습니다. &quot;더 보고 싶다&quot;는 욕구를 자극하는 것이 포인트입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;2. AppDownloadModal &amp;mdash; 스토어 선택 팝업&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;새로 만든 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;AppDownloadModal&lt;/code&gt; 컴포넌트는 잠긴 카드를 탭했을 때 나타나는 팝업입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #5c6bc0; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;모달 구성 요소&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힌트 텍스트&lt;/b&gt; &amp;mdash; 어떤 카드에서 왔는지 컨텍스트 제공 (예: &quot;에너지 균형을 앱에서 자세히 보기&quot;)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;App Store / Google Play 뱃지&lt;/b&gt; &amp;mdash; 스토어별 다운로드 링크&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ESC 닫기, 배경 클릭 닫기&lt;/b&gt; &amp;mdash; 기본 UX 처리&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;14개 언어 다국어 지원&lt;/b&gt; &amp;mdash; 기존 i18n 시스템 활용&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;3. 하단 고정 다운로드 CTA 바&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;FloatingLoginBar&lt;/code&gt;로 &quot;로그인하고 더 보기&quot;를 유도했는데, 이것을 완전히 제거하고 눈에 띄는 &lt;b&gt;오렌지 그라데이션 다운로드 바&lt;/b&gt;로 교체했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 화면 하단에 고정 (fixed bottom)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;✓ safe-area-inset 대응 (노치/홈 인디케이터 고려)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 다운로드 아이콘 + &quot;앱에서 나의 전체 분석 보기&quot; 문구&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 탭하면 AppDownloadModal 표시&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;4. 블러 프리미엄 카드 전환&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면 하단에는 블러 처리된 프리미엄 카드 3종(맞춤 해석, 오늘의 조언, 크라우드 통계)이 있었습니다. 기존에는 이 카드들이 로그인 페이지로 연결되었는데, 공개 페이지에서는 로그인보다 &lt;b&gt;앱 다운로드가 더 직접적인 전환 경로&lt;/b&gt;이므로 모두 다운로드 모달로 전환했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;변경 포인트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;lt;Link href=&quot;/login&quot;&amp;gt;&lt;/code&gt; 을 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;lt;button onClick={openDownload}&amp;gt;&lt;/code&gt; 으로 전환. 각 카드별 source 파라미터로 GA 이벤트도 추적합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;5. DiscoverCarousel 카드도 다운로드 유도&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;다른 분석 도구(궁합, 판타지 캐릭터 등)를 소개하는 캐러셀 카드도 공개 페이지에서는 해당 페이지로의 링크 대신 다운로드 모달로 유도하도록 변경했습니다. &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;onDownloadClick&lt;/code&gt; prop을 전달하면 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;lt;Link&amp;gt;&lt;/code&gt;가 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;lt;button&amp;gt;&lt;/code&gt;으로 바뀌고, 모든 카드에 잠금 오버레이가 표시됩니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===== 섹션 4: GA 이벤트 설계 ===== --&gt;
&lt;p style=&quot;color: #e64a19; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;퍼널_추적&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;GA 이벤트 설계 &amp;mdash; 터치포인트별 추적&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 요소가 가장 많은 전환을 만드는지 측정하기 위해, 모든 터치포인트에 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;trackCtaClick&lt;/code&gt; 이벤트를 배치했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;터치포인트별 GA 이벤트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;analysis_card_{key}&lt;/b&gt; &amp;mdash; 인사이트 카드 12종 각각 (energy, balance, harmony ...)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;analysis_locked_interpretation&lt;/b&gt; &amp;mdash; 맞춤 해석 블러 카드&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;analysis_locked_advice&lt;/b&gt; &amp;mdash; 오늘의 조언 블러 카드&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;analysis_locked_crowd&lt;/b&gt; &amp;mdash; 크라우드 통계 블러 카드&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;discover_{slug}&lt;/b&gt; &amp;mdash; 캐러셀 카드 (궁합, 판타지 등)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;analysis_floating_download&lt;/b&gt; &amp;mdash; 하단 고정 CTA 바&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app_store_click / google_play_click&lt;/b&gt; &amp;mdash; 모달 내 스토어 버튼 (+ source 파라미터)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff8e1; border-left: 4px solid #ffd600; border-radius: 0 12px 12px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #f57f17; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 포인트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;source 파라미터 덕분에 &quot;사용자가 어떤 카드를 탭해서 모달까지 왔고, 거기서 어느 스토어를 선택했는지&quot;까지 추적할 수 있습니다. 이 데이터로 어떤 카드가 전환에 가장 기여하는지 분석이 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===== 섹션 5: 결과와 교훈 ===== --&gt;
&lt;p style=&quot;color: #e64a19; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;결과_교훈&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;결과와 교훈&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;콘텐츠 잠금 적용 후, 웹 랜딩페이지에서 앱 스토어로의 전환율이 크게 개선되었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #2e7d32; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 변화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 이전에는 결과를 보고 이탈했지만, 이제 &quot;더 보고 싶다&quot;는 동기가 생김&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 모달까지 도달한 사용자의 상당수가 실제 스토어로 이동&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 하단 고정 CTA 바가 가장 높은 클릭 비율 기록&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 얻은 교훈을 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;무료 콘텐츠의 &quot;적정 수준&quot;이 있다&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;너무 많이 보여주면 다운로드할 이유가 없고, 너무 적게 보여주면 관심을 잃습니다. 저희 경우에는 &lt;b&gt;카드의 제목과 서브타이틀은 보여주고, 상세 내용은 앱에서만&lt;/b&gt; 볼 수 있게 하는 것이 최적의 균형이었습니다. 사용자가 &quot;이건 좋은 정보인데, 더 보고 싶다&quot;고 느끼게 만드는 것이 핵심입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;CTA는 &quot;맥락&quot;이 중요하다&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 &quot;앱 다운로드&quot; 버튼을 놓는 것보다, 사용자가 보고 싶은 콘텐츠와 연결된 CTA가 훨씬 효과적입니다. &quot;앱에서 에너지 균형 자세히 보기&quot;라는 문구는 &quot;앱 다운로드&quot;보다 훨씬 구체적이고 설득력 있습니다. 이를 위해 백엔드의 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;metaphor.label&lt;/code&gt;을 활용한 동적 템플릿을 구현했습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;퍼널 데이터 없이는 최적화할 수 없다&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GA4 퍼널 분석이 없었다면, 이탈 지점이 &quot;결과 확인 후&quot;라는 것을 정확히 파악하지 못했을 것입니다. 감으로 &quot;다운로드 버튼을 크게 만들자&quot; 같은 시도를 했을 텐데, 문제의 본질은 버튼 크기가 아니라 &lt;b&gt;다운로드할 동기 자체가 없었다&lt;/b&gt;는 것이었습니다. 데이터 기반 의사결정이 핵심입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;기존 컴포넌트를 활용하되 목적만 전환하라&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;FloatingLoginBar, InsightCard, DiscoverCarousel 등 기존 컴포넌트를 크게 바꾸지 않고, &quot;로그인 유도&quot; &amp;rarr; &quot;다운로드 유도&quot;로 목적만 전환했습니다. 새로 만든 것은 AppDownloadModal 하나뿐입니다. 최소한의 변경으로 최대의 효과를 얻는 것이 실전에서 중요합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===== 섹션 6: 마무리 ===== --&gt;
&lt;p style=&quot;color: #e64a19; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;마무리&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;이번 작업의 핵심 사이클은 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. GA 퍼널로 문제 진단&lt;/b&gt; &amp;mdash; 어디서 이탈하는지 데이터로 확인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 콘텐츠 잠금으로 해결&lt;/b&gt; &amp;mdash; 적정 수준의 무료 콘텐츠 + 앱 다운로드 유도&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 다시 퍼널로 결과 측정&lt;/b&gt; &amp;mdash; 터치포인트별 이벤트로 효과 검증&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;웹-투-앱 전환율 최적화는 단순한 UI 변경이 아닙니다. &quot;사용자가 무엇을 원하는지&quot;와 &quot;어디서 그 욕구를 채워줄지&quot;의 균형을 맞추는 작업입니다. 콘텐츠 잠금은 그 균형을 만드는 가장 직접적인 방법 중 하나입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;비슷한 고민을 하고 계시다면, 먼저 GA 퍼널부터 세팅해보시길 추천합니다. 이탈 지점을 알면, 해결책은 생각보다 단순할 수 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #e64a19; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 20px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #fbe9e7; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;웹-투-앱 전환, GA4 퍼널 설정, 콘텐츠 잠금 전략에 대해 궁금한 점이 있으시면 댓글로 남겨주세요.&lt;/p&gt;
&lt;p style=&quot;color: #fbe9e7; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택: Next.js (App Router) + TypeScript + Zustand + GA4&lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/마케팅 &amp;middot; 수익화</category>
      <category>Content Gating</category>
      <category>cta 최적화</category>
      <category>GA4 퍼널</category>
      <category>next.js</category>
      <category>모바일 앱 유입</category>
      <category>앱 다운로드 전환율</category>
      <category>앱 마케팅</category>
      <category>웹투앱 전환</category>
      <category>전환율 최적화</category>
      <category>콘텐츠 잠금</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/658</guid>
      <comments>https://kwonputer.tistory.com/658#entry658comment</comments>
      <pubDate>Mon, 13 Apr 2026 12:17:32 +0900</pubDate>
    </item>
    <item>
      <title>10개 다국어 블로그 운영 D+59 리포트 &amp;mdash; 방문자 1,091명, 검색노출 1,025회 달성</title>
      <link>https://kwonputer.tistory.com/657</link>
      <description>&lt;div style=&quot;text-align: center; padding: 40px 20px 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #6b7280; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;D+59 &amp;middot; 2026년 04월 01일 기준&lt;/p&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;D+59, AI 블로그 실험 2개월차 리포트&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 10px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 작성한 콘텐츠만으로 10개국 블로그를 운영하는 실험이 시작된 지 59일이 지났습니다. 2개월을 조금 넘긴 시점에서 각 블로그들이 서서히 검색엔진의 인덱싱을 받기 시작했어요. 과연 AI만으로도 진짜 독자들을 끌어올 수 있을까요?&lt;/p&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;지난 59일간 10개 블로그를 합쳐서 총 1,091명이 방문해주셨고, 1,518번의 페이지뷰를 기록했습니다. 검색엔진에서는 1,025회 노출되며 서서히 존재감을 드러내고 있네요. 하루 평균 18명 정도가 찾아오는 작은 시작이지만, 언어별로 들여다보면 흥미로운 패턴들이 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;h2 style=&quot;font-size: 22px; color: #1e293b; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size26&quot;&gt;12월 블로그 성과 스냅샷&lt;/h2&gt;
&lt;div style=&quot;height: 5px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; border: 1px solid #e2e8f0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;background-color: #475569;&quot;&gt;
&lt;th style=&quot;padding: 12px; text-align: left; color: #ffffff; font-size: 14px;&quot;&gt;블로그&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: left; color: #ffffff; font-size: 14px;&quot;&gt;언어&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: right; color: #ffffff; font-size: 14px;&quot;&gt;방문자&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: right; color: #ffffff; font-size: 14px;&quot;&gt;페이지뷰&lt;/th&gt;
&lt;th style=&quot;padding: 12px; text-align: right; color: #ffffff; font-size: 14px;&quot;&gt;검색 노출&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com&quot;&gt;Kwonputer&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;한국어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;87&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;295&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com&quot;&gt;Kwonglish&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;영어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;129&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;195&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;324&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com&quot;&gt;Kwonnen&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;독일어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;96&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;136&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;127&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com&quot;&gt;Kwonteki&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;일본어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;84&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;105&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com&quot;&gt;Kwontenu&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;프랑스어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;94&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;110&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com&quot;&gt;Kwonsejo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;스페인어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;166&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;206&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com&quot;&gt;Kwontudo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;포르투갈어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;139&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;158&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnis.com&quot;&gt;Kwonnis&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;네덜란드어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;90&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;97&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontento.com&quot;&gt;Kwontento&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;이탈리아어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;108&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;114&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; font-weight: bold;&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com&quot;&gt;Kwontrol&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; color: #6b7280;&quot;&gt;터키어&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;98&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;102&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; border-bottom: 1px solid #e2e8f0; text-align: right;&quot;&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #1e293b;&quot;&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; font-weight: bold;&quot; colspan=&quot;2&quot;&gt;합계&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; text-align: right; font-weight: bold;&quot;&gt;1,091&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; text-align: right; font-weight: bold;&quot;&gt;1,518&lt;/td&gt;
&lt;td style=&quot;padding: 10px 12px; color: #ffffff; text-align: right; font-weight: bold;&quot;&gt;1,025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 13px; color: #9ca3af; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;* 방문자&amp;middot;페이지뷰: Google Analytics / 검색 노출: Google Search Console&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;h2 style=&quot;font-size: 22px; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size26&quot;&gt;각 블로그를 좀 더 자세히 보면&lt;/h2&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. Kwonputer &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 한국어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com&quot;&gt;https://kwonputer.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/ko-homepage.png&quot; alt=&quot;Kwonputer 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;87&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;295&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;32.2%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;79&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;4&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;aws프리티어&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;유튜브 프리미엄 우회 클리앙&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com/docker-docker-입문-완벽-가이드-개발-환경-세팅부터-배포까/&quot;&gt;[Docker] Docker 입문 완벽 가이드 &amp;mdash; 개발 환경 세팅부터 배포까지 - 권...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com/git-github-입문-완벽-가이드-개발자-필수-도구-2/&quot;&gt;Git &amp;amp; GitHub 입문 완벽 가이드 &amp;mdash; 개발자 필수 도구 - 권퓨터: Kwonp...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonputer.com/무료-vpn-추천-top-10-안전하고-빠른-vpn-총정리-2026-2/&quot;&gt;무료 VPN 추천 TOP 10 &amp;mdash; 안전하고 빠른 VPN 총정리 (2026) - 권퓨터...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;개발 튜토리얼이 인기를 끌며 295 PV 달성! Docker 가이드가 특히 좋은 반응을 보여드리고 있네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. Kwonglish &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 영어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com&quot;&gt;https://kwonglish.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/en-homepage.png&quot; alt=&quot;Kwonglish 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;129&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;195&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;16.9%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;324&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;best hidden gem restaurants nyc&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;dell xps vs macbook pro 2025 2026 comparison which is better&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;dell xps vs macbook pro comparison 2025 or 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;dell xps vs macbook pro comparison 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;hidden gem nyc restaurants&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com/docker-kubernetes-migration-guide-2026/&quot;&gt;[DevOps] Complete Docker to Kubernetes Migratio...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com/marketing-monetization-app-store-optimization-aso-guide-for-indie-developers-in-2026/&quot;&gt;[Marketing &amp;amp; Monetization] App Store Optimizati...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonglish.com/productivity-the-ultimate-developer-workflow-setup-2026-vs-code-extensions-terminal-tools-and-automation-scripts/&quot;&gt;[Productivity] The Ultimate Developer Workflow ...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;검색 노출 324회로 가장 높은 visibility 확보! 뉴욕 맛집 검색어가 의외로 인기입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. Kwonnen &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 독일어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com&quot;&gt;https://kwonnen.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/de-homepage.png&quot; alt=&quot;Kwonnen 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;96&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;136&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;23.0%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;127&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;progressive web app 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;postgrest&quot;&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;andt design&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;ant design vs material ui&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;cafe laptop berlin&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com/best-psychological-anime-2026-recommendations/&quot;&gt;[Anime] Die besten psychologischen Anime 2026: ...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com/die-7-besten-geheimtipp-restaurants-in-berlin-lokale-favoriten-die-man-probieren-muss-2/&quot;&gt;Die 7 besten Geheimtipp-Restaurants in Berlin &amp;mdash;...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnen.com/die-7-besten-geheimtipp-restaurants-in-berlin-lokale-favoriten-die-man-probieren-muss/&quot;&gt;Die 7 besten Geheimtipp-Restaurants in Berlin &amp;mdash;...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;베를린 맛집 콘텐츠가 96명의 방문자를 이끌어왔어요. 심리 애니메이션 글도 주목받고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;4. Kwonteki &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 일본어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com&quot;&gt;https://kwonteki.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/ja-homepage.png&quot; alt=&quot;Kwonteki 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;84&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;105&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;13.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;35&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;next.js 15 app router documentation&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;next.js 15 app router official docs&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;パズル プログラミング&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;プログラミング パズル&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com/東京で本当に美味しい隠れ家レストラン7選-地/&quot;&gt;東京で本当に美味しい隠れ家レストラン7選 &amp;mdash; 地元民が通う名店ガイド - Kwonteki&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com/openai-gpt4o-claude-gemini-developer-comparison-2026/&quot;&gt;[AI・ML] OpenAI GPT-4o vs Claude 3.5 Sonnet vs G...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonteki.com/best-anime-2026-must-watch/&quot;&gt;[アニメ] 2026年絶対見るべき！感動と興奮が止まらないおすすめアニメ10選 - Kwonteki&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 15 관련 검색이 꾸준해요. 도쿄 맛집 가이드와 AI 비교 글이 84명을 끌어모았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;5. Kwontenu &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 프랑스어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com&quot;&gt;https://kwontenu.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/fr-homepage.png&quot; alt=&quot;Kwontenu 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;94&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;110&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;9.5%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;76&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;(inurl:comment) collisions&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;framework laptop&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;framework+laptop&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;laptop framework&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;pc portable d&amp;eacute;veloppeur&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontenu.com/flutter-comment-optimiser-les-performances-de-votre-application-mobile-en-2026/&quot;&gt;[Flutter] Comment optimiser les performances de...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter 성능 최적화 글이 94명의 관심을 받았네요. Framework 노트북 검색도 흥미로운 트렌드입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;6. Kwonsejo &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 스페인어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com&quot;&gt;https://kwonsejo.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/es-homepage.png&quot; alt=&quot;Kwonsejo 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;166&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;206&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;9.5%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;88&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;2&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;&quot;data version control&quot; -site:reddit.com -site:twitter.com -site:x.com -site:wykop.pl -site:tripadvisor.com -site:youtube.com -site:yelp.com -site:booking.com -site:facebook.com -site:instagram.com -site:tiktok.com&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;anime&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;helldivers 2 builds 2026&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;p&amp;aacute;gina de anime&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com/gaming-helldivers-2-guia-de-estratagemas-y-builds-para-la-victoria-en-2026/&quot;&gt;[Gaming] Helldivers 2: Gu&amp;iacute;a de Estratagemas y B...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com/crear-chatbot-langchain-openai-gpt4o-tutorial/&quot;&gt;[IA &amp;amp; ML] C&amp;oacute;mo crear tu primer chatbot con Lang...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonsejo.com/tailwind-css-guia-completa-2026/&quot;&gt;[Frontend] Gu&amp;iacute;a completa de Tailwind CSS en 202...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;Helldivers 2 콘텐츠가 대박! 166명 방문으로 스페인어 블로그 중 최고 성과를 기록했어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;7. Kwontudo &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 포르투갈어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontudo.com&quot;&gt;https://kwontudo.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/pt-homepage.png&quot; alt=&quot;Kwontudo 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;139&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;158&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;7.5%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;120&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;shakti&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;139명이 방문했지만 아직 히트 글은 없네요. Shakti 검색어가 유일한 단서입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;8. Kwonnis &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 네덜란드어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwonnis.com&quot;&gt;https://kwonnis.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/nl-homepage.png&quot; alt=&quot;Kwonnis 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;90&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;97&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;6.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;43&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;apple m4 vs intel core ultra benchmarks&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;apple m4 vs intel core ultra performance comparison&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;Apple M4 vs Intel 비교 검색이 인기! 90명이 방문했지만 콘텐츠 발굴이 필요해 보여요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;9. Kwontento &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 이탈리아어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontento.com&quot;&gt;https://kwontento.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/it-homepage.png&quot; alt=&quot;Kwontento 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;108&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;114&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;8.0%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;75&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
&lt;p style=&quot;padding: 0; line-height: 2.2;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;come fare un caff&amp;egrave; perfetto&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;cosa ti serve per fare il caff&amp;egrave; a casa&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;fare un buon caff&amp;egrave; espresso in casa&lt;/span&gt;, &lt;span style=&quot;background-color: #e0e7ff; padding: 2px 8px; font-size: 13px; color: #3730a3;&quot;&gt;miglior metodo di preparazione del caff&amp;egrave;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;이탈리아다운 커피 만들기 검색이 108명을 끌어왔어요. 완벽한 에스프레소에 대한 관심이 뜨겁네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 1px; background-color: #e2e8f0;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;height: 15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #f1f5f9; padding: 15px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #1e293b; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;10. Kwontrol &lt;span style=&quot;font-size: 14px; font-weight: normal; color: #64748b;&quot;&gt;&amp;mdash; 터키어&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 5px 0 0 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com&quot;&gt;https://kwontrol.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; text-align: center;&quot;&gt;&lt;img style=&quot;width: 100%; max-width: 700px; border: 1px solid #e2e8f0;&quot; src=&quot;https://cdn.kwonputer.com/reports/2026-04-01/tr-homepage.png&quot; alt=&quot;Kwontrol 홈페이지&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;방문자 &lt;b&gt;98&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;102&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;6.9%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;58&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 5px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #6b7280; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com/notion-ai-chatgpt-claude-comparison-guide/&quot;&gt;[Verimlilik] Notion AI vs ChatGPT vs Claude: İ&amp;ccedil;...&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; padding: 3px 0;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a style=&quot;color: #2563eb; text-decoration: none;&quot; href=&quot;https://kwontrol.com/ucretsiz-burc-uygulamasi-onerisi-fortie-astroloji-uyumluluk-tahminler/&quot;&gt;&amp;Uuml;cretsiz bur&amp;ccedil; uygulaması &amp;ouml;nerisi - Fortie: astr...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 10px 20px; background-color: #fefce8;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #854d0e; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;Notion vs AI 도구 비교글과 점성술 앱 리뷰로 98명 확보! 실용적 콘텐츠가 통하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height: 30px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 20px;&quot;&gt;
&lt;h2 style=&quot;font-size: 22px; color: #1e293b; padding: 0 0 5px 0;&quot; data-ke-size=&quot;size26&quot;&gt;59일차, 천명 돌파의 의미&lt;/h2&gt;
&lt;div style=&quot;height: 5px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;59일 만에 누적 방문자 1,091명을 기록했네요. Kwonsejo가 166명으로 단일 블로그 최고 기록을, Kwonglish가 324회 검색 노출로 SEO 최강자 자리를 차지했습니다.&lt;/p&gt;
&lt;div style=&quot;height: 8px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; color: #374151; line-height: 1.8; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;아직 306일이 더 남았지만, 이미 각 블로그만의 색깔이 보이기 시작해요. 어떤 블로그가 연말까지 가장 놀라운 성장을 보여줄지 지켜봐 주세요!&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;padding: 15px 20px; background-color: #f8f9fa; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 12px; color: #9ca3af; padding: 0;&quot; data-ke-size=&quot;size16&quot;&gt;2026년 04월 01일 기준 데이터 &amp;middot; Google Analytics + Search Console&lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/마케팅 &amp;middot; 수익화</category>
      <category>1인 개발 블로그</category>
      <category>SEO 최적화</category>
      <category>구글 서치콘솔</category>
      <category>구글 애널리틱스</category>
      <category>다국어 블로그</category>
      <category>블로그 성장 기록</category>
      <category>블로그 수익화</category>
      <category>블로그 운영 리포트</category>
      <category>블로그 트래픽 분석</category>
      <category>워드프레스 블로그</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/657</guid>
      <comments>https://kwonputer.tistory.com/657#entry657comment</comments>
      <pubDate>Wed, 1 Apr 2026 10:07:54 +0900</pubDate>
    </item>
    <item>
      <title>AI로 10개국어 블로그를 혼자 운영하는 방법 &amp;mdash; 자동화 시스템 구축 전체 공개</title>
      <link>https://kwonputer.tistory.com/656</link>
      <description>&lt;!-- =============================================
   제목: AI로 10개국어 블로그를 혼자 운영하는 방법 — 자동화 시스템 구축 전체 공개 (아키텍처·버그·SEO·로고·리포트까지)
   카테고리: IT·테크 &gt; 도구·생산성
   태그: 블로그자동화, AI블로그, 다국어블로그운영, 블로그수익화, 워드프레스자동화, SEO최적화방법, 1인개발, 사이드프로젝트, 콘텐츠자동화, 블로그운영팁
   티스토리 주제: IT &gt; IT 인터넷
   ============================================= --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #2563eb; border-radius: 16px; padding: 32px 40px;&quot;&gt;
&lt;p style=&quot;color: #93c5fd; font-size: 13px; letter-spacing: 3px; font-weight: 600; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;PROJECT OVERVIEW&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 26px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AI로 10개국어 블로그를 혼자 운영하는 방법&lt;/p&gt;
&lt;p style=&quot;color: #bfdbfe; font-size: 16px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Claude AI + GPT-4o + WordPress REST API + Cloudflare R2로 구축한 다국어 콘텐츠 자동 발행 인프라. 아키텍처 설계부터 버그 수정, 브랜드 로고 제작, SEO 개선, 운영 리포트까지 &amp;mdash; 구축 과정 전체를 공개합니다.&lt;/p&gt;
&lt;p style=&quot;color: #bfdbfe; font-size: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 키워드: &lt;b&gt;AI 콘텐츠 생성&lt;/b&gt;, &lt;b&gt;WordPress 자동화&lt;/b&gt;, &lt;b&gt;다국어 SEO&lt;/b&gt;, &lt;b&gt;Python 스케줄러&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px;&quot;&gt;
&lt;h2 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size26&quot;&gt;이 글의 순서&lt;/h2&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;1. 왜 이 시스템을 만들었나 &amp;mdash; 아이디어의 출발점&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;2. 전체 아키텍처 &amp;mdash; 10개 서버, 10개 언어, 1개 코드베이스&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;3. AI 콘텐츠 생성 파이프라인 &amp;mdash; Claude + GPT-4o 조합&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;4. 썸네일 자동 생성 &amp;mdash; Pillow + AI 이미지&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;5. Cloudflare R2 이미지 호스팅 마이그레이션&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;6. 자동 발행 스케줄러 &amp;mdash; FIFO 큐 + 랜덤 시간 배정&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;7. GitHub Actions CI/CD &amp;mdash; 10개 서버 동시 배포&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;8. SEO 자동화 &amp;mdash; Rank Math + 구조화 데이터&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;9. 발견하고 수정한 버그들&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;10. KWON 브랜드 로고 시스템 &amp;mdash; Pillow로 10개 블로그 로고 제작&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;11. AI 소설 자동 발행 시스템 &amp;mdash; 구현에서 폐기까지&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;12. 운영 리포트 시스템 &amp;mdash; GA4 + GSC &amp;rarr; HTML 자동 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;13. 법적 페이지 + 마이그레이션 도구 6종&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;14. 서버 운영 도구 &amp;mdash; sync.sh + LiteSpeed 자동화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;15. 10개 블로그 SEO 전수검사 &amp;mdash; 4단계 개선&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;16. Google Indexing API로 색인 가속화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;17. 현재 성과와 앞으로의 계획&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 대표 이미지 --&gt;
&lt;p&gt;&lt;img style=&quot;width: 100%; border-radius: 12px;&quot; src=&quot;https://cdn.kwonputer.com/ko/ai-multilingual-blog-2026/01-hero-automation.png&quot; alt=&quot;AI로 10개국어 블로그를 혼자 운영하는 자동화 시스템 개요&quot; width=&quot;1536&quot; height=&quot;1024&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: 배경 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;BACKGROUND&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 이 시스템을 만들었나&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;블로그로 수익을 내고 싶었습니다. 하지만 혼자서 10개 언어로 매일 포스트를 쓰는 건 사실상 불가능합니다. 그래서 생각을 바꿨습니다. &lt;b&gt;&quot;AI가 쓰면 되지 않나?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순했습니다. 한국어 블로그 하나에 AI로 글을 생성하고 WordPress에 올리는 스크립트. 그런데 만들다 보니 욕심이 생겼습니다. 어차피 코드는 같은데, 언어만 다르게 하면 10개 블로그를 동시에 운영할 수 있지 않을까?&lt;/p&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 아이디어&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 Python 코드베이스 + 언어별 설정 파일 = 10개 다국어 블로그 동시 운영. 서버는 언어별로 개별 Vultr VPS를 사용하고, GitHub Actions로 10개 서버에 동시 배포합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 현재 이 시스템은 &lt;b&gt;한국어(ko), 영어(en), 독일어(de), 일본어(ja), 프랑스어(fr), 스페인어(es), 포르투갈어(pt), 네덜란드어(nl), 이탈리아어(it), 터키어(tr)&lt;/b&gt; 10개 언어로 매일 블로그를 자동 발행하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;지금부터 이 시스템을 처음부터 끝까지 낱낱이 공개하겠습니다. 아키텍처 설계부터 골치 아팠던 버그들, SEO 최적화 과정까지 전부요.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: 아키텍처 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 전체 아키텍처&lt;/h2&gt;
&lt;p&gt;&lt;img style=&quot;width: 100%; border-radius: 12px;&quot; src=&quot;https://cdn.kwonputer.com/ko/ai-multilingual-blog-2026/02-architecture-diagram.png&quot; alt=&quot;10개 서버 다국어 블로그 자동화 아키텍처 &amp;mdash; Python, WordPress, Cloudflare R2&quot; width=&quot;1536&quot; height=&quot;1024&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;전체 구조를 한 문장으로 요약하면: &lt;b&gt;Vultr 10대 VPS에서 각각 Python 스케줄러가 2시간마다 실행되며, AI로 콘텐츠를 생성하고 WordPress REST API로 자동 발행합니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;인프라 구성&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버&lt;/b&gt; &amp;mdash; Vultr VPS 10대 (언어별 개별 서버, 각 $6/월)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DNS/CDN&lt;/b&gt; &amp;mdash; Cloudflare (각 언어 도메인에 적용)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CMS&lt;/b&gt; &amp;mdash; WordPress + LiteSpeed Cache + Rank Math SEO&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 스토리지&lt;/b&gt; &amp;mdash; Cloudflare R2 (커스텀 도메인 CDN)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 모델&lt;/b&gt; &amp;mdash; Claude Sonnet (콘텐츠 생성), GPT-4o (보조), GPT-image-1 (이미지)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CI/CD&lt;/b&gt; &amp;mdash; GitHub Actions (main 브랜치 push &amp;rarr; 10개 서버 동시 자동 배포)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #7C3AED; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;코드 구조&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;config/languages/{lang}/&lt;/b&gt; &amp;mdash; 언어별 설정 파일 (블로그 정보, 카테고리, 스타일, 프롬프트)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/generator/&lt;/b&gt; &amp;mdash; AI 콘텐츠 생성 파이프라인 (주제&amp;rarr;포스트&amp;rarr;썸네일)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/scheduler/&lt;/b&gt; &amp;mdash; FIFO 큐 기반 자동 발행 스케줄러&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/wordpress/&lt;/b&gt; &amp;mdash; WordPress REST API 클라이언트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/report/&lt;/b&gt; &amp;mdash; GA4 + GSC 기반 운영 리포트 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;scripts/&lt;/b&gt; &amp;mdash; 서버 동기화, SEO 마이그레이션, Indexing API 스크립트&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;단일 소스 원칙&lt;/b&gt;을 철저히 지켰습니다. 한 번 코드를 변경하면 GitHub Actions가 10개 서버에 자동으로 배포합니다. 각 서버는 같은 코드를 실행하지만, &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;LANGUAGE=ko&lt;/code&gt; (또는 en, de, ja...) 환경변수 하나로 언어가 결정됩니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;배포 자동화 워크플로우&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;git push &amp;rarr; GitHub Actions 트리거 &amp;rarr; matrix strategy로 10개 서버 병렬 SSH 배포 &amp;rarr; 각 서버에서 pip install + systemctl reload. 전체 배포 시간 약 2분.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: AI 생성 파이프라인 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;AI PIPELINE&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;3. AI 콘텐츠 생성 파이프라인&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;콘텐츠 생성은 크게 3단계로 이루어집니다. 주제 선정 &amp;rarr; 포스트 본문 생성 &amp;rarr; 썸네일 생성. 각 단계를 살펴보겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 주제 선정&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;주제는 카테고리 분류 체계를 기반으로 AI가 자동 선정합니다. 개발 / IT&amp;middot;테크 / 자기계발 / 생활정보 / 게임 / 애니 등 대분류에 소분류가 있고, 중복 발행을 방지하기 위해 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;topic_history.json&lt;/code&gt;에 발행 이력을 저장합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;문제&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 같은 주제를 중복으로 제안하는 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Claude에게 &quot;새로운 주제를 제안해줘&quot;라고 하면 이미 발행한 주제와 유사한 것을 추천하는 경우가 많았습니다. 특히 인기 있는 주제(AI 도구, 개발 생산성 등)는 반복 추천 빈도가 높았습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; 발행 이력을 프롬프트에 주입&lt;/p&gt;
&lt;pre class=&quot;ini&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# topic_history.json에서 최근 50개 주제 추출
recent_topics = history.get_recent(50)

# 프롬프트에 이력 주입
prompt = f&quot;&quot;&quot;
다음은 이미 발행된 주제입니다. 절대 중복되지 않게 새 주제를 제안하세요:
{recent_topics}
&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 포스트 본문 생성&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;본문은 Claude Sonnet이 생성합니다. 단순히 &quot;글 써줘&quot;가 아니라, 언어별로 세밀하게 조정된 시스템 프롬프트가 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;언어팩 구조 (config/languages/{lang}/)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;blog.json&lt;/b&gt; &amp;mdash; 블로그 이름, 도메인, 언어 코드, 로케일&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;categories.json&lt;/b&gt; &amp;mdash; 해당 언어권에 최적화된 카테고리 목록&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;styles.json&lt;/b&gt; &amp;mdash; 색상 팔레트 (언어별로 브랜드 컬러가 다름)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prompts.py&lt;/b&gt; &amp;mdash; 해당 언어로 작성된 시스템 프롬프트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;examples/&lt;/b&gt; &amp;mdash; 언어별 포스트 HTML 예시 파일&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 포인트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;독일어 블로그는 독일어 프롬프트로 독일어 독자를 위한 콘텐츠를 생성합니다. 단순 번역이 아니라, 해당 언어권의 문화&amp;middot;법률&amp;middot;생활환경에 맞게 현지화된 글이 생성됩니다. 예를 들어 스페인어 블로그의 부동산 포스트는 스페인 임대차법을 기준으로 작성됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;HTML 템플릿은 &lt;b&gt;5종&lt;/b&gt;으로 다변화되어 있습니다. 카드형, 매거진형, 미니멀형, 클래식형 등. 매번 같은 레이아웃이면 중복 콘텐츠로 인식될 수 있기 때문에 다양성을 확보했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;사전조사 기반 생성&lt;/b&gt; 기능도 있습니다. &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;claude-web/&lt;/code&gt; 폴더에 마크다운 파일을 넣으면, 해당 조사 내용을 바탕으로 더 깊이 있는 포스트가 생성됩니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: 썸네일 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;THUMBNAIL&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;4. 썸네일 자동 생성&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;썸네일은 Pillow(Python 이미지 라이브러리)로 생성합니다. AI가 생성한 배경 이미지 위에 제목 텍스트를 오버레이하는 방식입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;썸네일 생성 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계&lt;/b&gt; &amp;mdash; GPT-image-1로 포스트 주제에 맞는 배경 이미지 생성 (1280&amp;times;720)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계&lt;/b&gt; &amp;mdash; Pillow로 배경 이미지 위에 제목 텍스트 오버레이&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단계&lt;/b&gt; &amp;mdash; WebP 변환 (파일 크기 최적화)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4단계&lt;/b&gt; &amp;mdash; Cloudflare R2에 업로드 후 CDN URL 획득&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;폰트 문제와 해결&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;썸네일 개발 과정에서 폰트 이슈가 많았습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;macOS에서 한국어 폰트 깨짐&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Hiragino 폰트로 한국어를 렌더링하면 글자가 깨지는 현상. 로컬 개발 환경(macOS)과 서버(Linux) 환경의 폰트가 달라 생기는 문제였습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; OS 감지 후 폰트 자동 선택&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;import platform

if platform.system() == &quot;Darwin&quot;:
    # macOS: Apple SD Gothic Neo
    font_path = &quot;/System/Library/Fonts/AppleSDGothicNeo.ttc&quot;
else:
    # Linux (서버): Noto Sans CJK
    font_path = &quot;/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;다국어 썸네일 폰트 선택 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;10개 언어가 각각 다른 폰트를 필요로 합니다. CJK(한국어, 일본어)는 Noto Sans CJK, 라틴 계열(영어, 독일어 등)은 Montserrat나 Noto Sans를 사용해야 합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; 언어 코드 기반 폰트 자동 선택&lt;/p&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;CJK_LANGS = {&quot;ko&quot;, &quot;ja&quot;, &quot;zh&quot;}

def get_font_path(language: str) -&amp;gt; str:
    if language in CJK_LANGS:
        return NOTO_SANS_CJK_PATH
    else:
        return NOTO_SANS_LATIN_PATH&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;썸네일 이모지 ☒ 렌더링 버그&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 생성한 포스트 제목에 이모지가 포함될 경우 Pillow가 렌더링하지 못해 □(빈 박스)가 표시되는 문제. Noto Color Emoji 폰트가 없는 서버에서 특히 심각했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; 썸네일 생성 전 이모지 제거&lt;/p&gt;
&lt;pre class=&quot;processing&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;import re

def strip_emoji(text: str) -&amp;gt; str:
    emoji_pattern = re.compile(
        &quot;[\U00010000-\U0010FFFF]&quot;,
        flags=re.UNICODE
    )
    return emoji_pattern.sub(&quot;&quot;, text).strip()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: Cloudflare R2 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;IMAGE HOSTING&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;5. Cloudflare R2 이미지 호스팅 마이그레이션&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 이미지를 GitHub 레포에 저장했습니다. 포스트당 5~7개 이미지, 10개 블로그에서 매일 발행하면 금방 레포 용량이 폭발합니다. 게다가 GitHub은 이미지 CDN으로 적합하지 않습니다. 결국 &lt;b&gt;Cloudflare R2로 전면 마이그레이션&lt;/b&gt;을 결정했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;R2 선택 이유&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무료 이그레스&lt;/b&gt; &amp;mdash; Cloudflare CDN을 통한 트래픽은 무료 (AWS S3는 GB당 과금)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 도메인&lt;/b&gt; &amp;mdash; images.kwonputer.com 등 도메인 연결 가능&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;S3 호환 API&lt;/b&gt; &amp;mdash; boto3로 그대로 사용 가능 (코드 변경 최소)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;글로벌 CDN&lt;/b&gt; &amp;mdash; 10개 언어 사용자 모두에게 빠른 이미지 로딩&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;마이그레이션의 핵심은 &lt;b&gt;발행 후 로컬 이미지 자동 정리&lt;/b&gt;입니다. 포스트를 WordPress에 발행하면 이미지가 R2에 업로드되고, 로컬의 임시 이미지 파일은 자동으로 삭제됩니다. 서버 용량을 거의 차지하지 않습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;이미지 처리 흐름&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;AI 이미지 생성 &amp;rarr; WebP 변환 &amp;rarr; R2 업로드 &amp;rarr; CDN URL 획득 &amp;rarr; HTML에 삽입 &amp;rarr; 로컬 파일 삭제. 이미지 병렬 처리로 생성 시간도 크게 단축했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 6: 스케줄러 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;SCHEDULER&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;6. 자동 발행 스케줄러&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;스케줄러는 이 시스템의 심장입니다. 매일 정해진 수의 포스트를 랜덤한 시간에 자동 발행합니다. 사람이 발행하는 것처럼 자연스러운 패턴을 만들기 위해서입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;스케줄러 핵심 설계&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FIFO 큐&lt;/b&gt; &amp;mdash; 생성된 포스트는 큐에 순서대로 등록됨&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;랜덤 발행 시간&lt;/b&gt; &amp;mdash; 하루 발행 시간대(예: 07:00~22:00)에서 랜덤 시간 배정, 최소 3시간 간격&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cron 2시간 주기&lt;/b&gt; &amp;mdash; 서버 cron이 2시간마다 스케줄러 실행 &amp;rarr; 발행 예정 시간 도래 시 WP에 발행&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자동 재시도&lt;/b&gt; &amp;mdash; 발행 실패 시 자동 재시도, 3회 실패 시 영구 실패로 표시&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;헬스체크 + Slack 알림&lt;/b&gt; &amp;mdash; 큐 상태&amp;middot;발행 실패&amp;middot;콘텐츠 부족 시 Slack 알림&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;스케줄러 상태 확인 예시&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;$ python -m src scheduler status

전체 큐: 43개 | 대기: 9개 | 발행 완료: 34개
하루 발행 수: 3개 | 발행 시간대: 07:00 ~ 22:00
다음 발행: 2026-03-24T08:57 (EN - Local Restaurant Guide)
예상 소진: 3.0일 (9개 / 하루 3개)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;중복 발행 방지 &amp;mdash; 3중 방어 체계&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;실제 운영 중 가장 골치 아팠던 문제가 &lt;b&gt;중복 발행&lt;/b&gt;이었습니다. 같은 포스트가 두 번 올라가는 것은 SEO에 매우 치명적입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;3중 방어 체계&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1차 방어&lt;/b&gt; &amp;mdash; 로컬 메타데이터에서 이미 발행된 포스트 필터링&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2차 방어&lt;/b&gt; &amp;mdash; WordPress REST API로 slug 중복 확인 (draft/trash 포스트 포함)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3차 방어&lt;/b&gt; &amp;mdash; 발행 전 제목 해시값 비교로 최종 중복 차단&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fef2f2; border-left: 4px solid #B91C1C; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #b91c1c; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;트러블슈팅 포인트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;초기에 draft 상태의 포스트는 중복 체크에서 제외했더니 같은 글이 draft + published로 두 번 올라가는 문제가 발생. &lt;b&gt;draft와 trash 포스트도 중복 체크 대상에 포함&lt;/b&gt;하는 것으로 해결했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 7: CI/CD --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CI/CD&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;7. GitHub Actions로 10개 서버 동시 배포&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 수정할 때마다 10개 서버에 SSH로 하나씩 접속해서 배포하면... 생각만 해도 끔찍합니다. GitHub Actions의 &lt;b&gt;matrix strategy&lt;/b&gt;로 이 문제를 완전히 해결했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions matrix 전략&lt;/p&gt;
&lt;pre class=&quot;vim&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;strategy:
  matrix:
    server:
      - { lang: ko, host: &quot;server-ko.example.com&quot; }
      - { lang: en, host: &quot;server-en.example.com&quot; }
      - { lang: de, host: &quot;server-de.example.com&quot; }
      # ... 10개 서버

steps:
  - name: Deploy to ${{ matrix.server.lang }}
    run: |
      rsync -avz --exclude='.env' \
        src/ config/ requirements.txt \
        root@${{ matrix.server.host }}:/opt/tistory-blog-auto/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;배포 제외 항목&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;각 서버의 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;data/posts_metadata.json&lt;/code&gt;, &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;data/publish_schedule.json&lt;/code&gt;, &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;posts/&lt;/code&gt; 폴더는 배포 제외입니다. 서버별로 독립적인 데이터이기 때문입니다. 코드만 배포하고 데이터는 각 서버가 자체 관리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 8: SEO 자동화 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;SEO AUTOMATION&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;8. SEO 자동화 &amp;mdash; Rank Math + 구조화 데이터&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;SEO는 블로그 수익화의 핵심입니다. 자동으로 생성되는 포스트라도 SEO 점수가 높아야 합니다. 다음은 자동화된 SEO 항목들입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;자동 처리되는 SEO 항목&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포커스 키워드&lt;/b&gt; &amp;mdash; AI가 포스트 주제에서 추출, Rank Math에 자동 설정&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SEO 메타 설명&lt;/b&gt; &amp;mdash; 120~160자 범위로 AI 생성, 발행 전 길이 검증&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SEO 슬러그&lt;/b&gt; &amp;mdash; 영문 키워드 기반 URL-friendly 슬러그 자동 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSON-LD 구조화 데이터&lt;/b&gt; &amp;mdash; Article, FAQ, BreadcrumbList 스키마 자동 삽입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 alt 텍스트&lt;/b&gt; &amp;mdash; 각 이미지에 맥락에 맞는 alt 텍스트 자동 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 width/height&lt;/b&gt; &amp;mdash; CLS 방지를 위한 크기 속성 자동 삽입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내부 링크&lt;/b&gt; &amp;mdash; FAQ 섹션 하단에 관련 포스트 자동 연결&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;FAQ 리치 스니펫 자동 삽입&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Google 검색 결과에서 &quot;사람들이 자주 묻는 질문&quot; 섹션으로 노출되는 FAQ 리치 스니펫을 모든 포스트에 자동으로 삽입합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;FAQ 스키마 구조&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;FAQPage&quot;,
  &quot;mainEntity&quot;: [
    {
      &quot;@type&quot;: &quot;Question&quot;,
      &quot;name&quot;: &quot;포스트 주제 관련 질문 1&quot;,
      &quot;acceptedAnswer&quot;: {
        &quot;@type&quot;: &quot;Answer&quot;,
        &quot;text&quot;: &quot;AI가 생성한 답변&quot;
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;Rank Math 사이트맵 캐시 버그&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;운영 중 발견한 중요한 버그였습니다. WordPress REST API로 포스트를 발행하면 Rank Math 사이트맵 캐시가 갱신되지 않아, 새 포스트가 사이트맵에 포함되지 않는 문제였습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 04&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REST API 발행 시 Rank Math 사이트맵 미갱신&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Rank Math는 WordPress 관리자에서 포스트를 발행할 때 사이트맵을 갱신합니다. 하지만 REST API로 발행하면 이 훅이 트리거되지 않아 사이트맵이 오래된 캐시를 유지합니다. 새 포스트가 Google에 색인되지 않는 치명적인 결과로 이어질 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; mu-plugin으로 REST API 발행 시 캐시 자동 삭제&lt;/p&gt;
&lt;pre class=&quot;php&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// mu-plugins/flush-sitemap-cache.php
add_action('rest_after_insert_post', function($post) {
    if ($post-&amp;gt;post_status === 'publish') {
        do_action('rank_math/sitemap/clear_cache');
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 9: 버그들 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;BUG FIXES&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;9. 발견하고 수정한 버그들&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;시스템을 운영하면서 만난 주요 버그들을 정리합니다. 비슷한 시스템을 구축하는 분들에게 도움이 될 것 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 05&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;LANGUAGE 환경변수 로딩 버그&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;서버에서 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;LANGUAGE=en&lt;/code&gt;으로 설정했는데 항상 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;ko&lt;/code&gt;로 동작하는 문제. settings.py에서 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;load_dotenv()&lt;/code&gt;를 호출하기 전에 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;os.getenv()&lt;/code&gt;를 먼저 읽어버려 .env 파일이 로드되지 않은 상태였습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; 모듈 최상단에서 load_dotenv() 먼저 호출&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;settings.py 최상단에 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;from dotenv import load_dotenv; load_dotenv()&lt;/code&gt; 추가로 해결.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 06&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;WP 카테고리 HTML 엔티티 디코딩 미처리&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;WordPress REST API에서 카테고리명을 가져오면 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;amp;amp;&lt;/code&gt;가 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;amp;&lt;/code&gt;로 표시되는 HTML 엔티티 인코딩 문제. 카테고리 매핑이 실패해 포스트가 잘못된 카테고리에 등록됐습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; html.unescape() 적용&lt;/p&gt;
&lt;pre class=&quot;clean&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;import html
category_name = html.unescape(raw_name)
# &quot;IT &amp;amp; 개발&quot; &amp;rarr; &quot;IT &amp;amp; 개발&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 07&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AI IMAGE 플레이스홀더 누락 시 이미지 미삽입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Claude가 HTML을 생성할 때 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;[AI IMAGE]&lt;/code&gt; 플레이스홀더를 빠뜨리는 경우가 있었습니다. 이 경우 이미지 삽입 로직이 동작하지 않아 이미지가 없는 포스트가 발행됐습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; h2 태그 기반 자동 플레이스홀더 삽입&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;플레이스홀더가 없으면 첫 번째 &amp;lt;h2&amp;gt; 태그 바로 아래에 자동으로 플레이스홀더를 삽입하는 fallback 로직 추가.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그 08&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;리포트 HTML &amp;lt;hr&amp;gt; 티스토리 호환성 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;운영 리포트를 티스토리에 올릴 때 섹션 간격으로 사용한 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; 태그가 티스토리 에디터에서 수평선으로 렌더링되는 문제. 레이아웃이 완전히 망가졌습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dcfce7; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #15803d; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 &amp;mdash; &amp;lt;hr&amp;gt; &amp;rarr; 투명 &amp;lt;div&amp;gt; 교체&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&amp;lt;!-- Before --&amp;gt;
&amp;lt;hr style=&quot;height: 60px;&quot;&amp;gt;

&amp;lt;!-- After --&amp;gt;
&amp;lt;div style=&quot;padding-top: 60px;&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 10: SEO 전수검사 --&gt;
&lt;p&gt;&lt;img style=&quot;width: 100%; border-radius: 12px;&quot; src=&quot;https://cdn.kwonputer.com/ko/ai-multilingual-blog-2026/03-seo-analytics.png&quot; alt=&quot;10개 다국어 블로그 SEO 전수검사 &amp;mdash; Google Search Console 노출/클릭 분석&quot; width=&quot;1536&quot; height=&quot;1024&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;SEO AUDIT&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;15. 10개 블로그 SEO 전수검사 &amp;mdash; 4단계 개선&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;블로그를 만든 지 약 30일 후, 전체 SEO 상태를 전수검사했습니다. Google Search Console 데이터를 보면서 깜짝 놀랐습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;검색 성과 (2026년 3월, 30일간)&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto;&quot;&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; font-size: 14px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background-color: #2563eb; color: #fff;&quot;&gt;
&lt;th style=&quot;padding: 10px 14px; text-align: left; border: 1px solid #1D4ED8;&quot;&gt;블로그&lt;/th&gt;
&lt;th style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #1D4ED8;&quot;&gt;노출&lt;/th&gt;
&lt;th style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #1D4ED8;&quot;&gt;클릭&lt;/th&gt;
&lt;th style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #1D4ED8;&quot;&gt;CTR&lt;/th&gt;
&lt;th style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #1D4ED8;&quot;&gt;평균순위&lt;/th&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 14px; border: 1px solid #e9ecef; color: #212529; font-weight: 600;&quot;&gt;Kwonglish (EN)&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;309&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #b91c1c; font-weight: bold;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;0.0%&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;8.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 10px 14px; border: 1px solid #e9ecef; color: #212529; font-weight: 600;&quot;&gt;Kwonnen (DE)&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;123&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;0.8%&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;16.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 14px; border: 1px solid #e9ecef; color: #212529; font-weight: 600;&quot;&gt;Kwontudo (PT)&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;98&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #b91c1c; font-weight: bold;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;0.0%&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;7.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 10px 14px; border: 1px solid #e9ecef; color: #212529; font-weight: 600;&quot;&gt;Kwonsejo (ES)&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;61&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #15803d; font-weight: bold;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #15803d; font-weight: bold;&quot;&gt;3.3%&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;8.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 10px 14px; border: 1px solid #e9ecef; color: #212529; font-weight: 600;&quot;&gt;Kwonputer (KO)&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;14&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #15803d; font-weight: bold;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #15803d; font-weight: bold;&quot;&gt;14.3%&lt;/td&gt;
&lt;td style=&quot;padding: 10px 14px; text-align: center; border: 1px solid #e9ecef; color: #495057;&quot;&gt;17.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; padding-top: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;10개 블로그 합산: 노출 798회, 클릭 5회 (30일간)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;EN 블로그가 309번이나 노출됐는데 클릭이 0이라는 게 충격이었습니다. 이건 단순히 콘텐츠 품질 문제가 아니라 &lt;b&gt;온페이지 SEO 세팅 문제&lt;/b&gt;였습니다. 전수검사를 통해 다음을 발견했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fef2f2; border-left: 4px solid #B91C1C; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #b91c1c; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;P0 치명적 문제 (즉시 수정)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9개 블로그 홈페이지 제목 깨짐&lt;/b&gt; &amp;mdash; tagline이 없어 &quot;Kwonglish -&quot; 처럼 끝남&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9개 블로그 홈페이지 메타 설명 없음&lt;/b&gt; &amp;mdash; Google이 자체 스니펫 생성 &amp;rarr; CTR 저하&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9개 블로그 OG 태그 불완전&lt;/b&gt; &amp;mdash; SNS 공유 시 미리보기 깨짐&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;4단계 개선 실행 결과&lt;/h3&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Phase 1 &amp;mdash; 긴급 수정&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 9개 블로그 tagline WP REST API로 자동 설정&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 9개 블로그 Rank Math 홈페이지 메타 설명 일괄 설정&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 9개 블로그 OG/Twitter Card 이미지 미디어 라이브러리 등록 + 설정&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Phase 2 &amp;mdash; E-E-A-T 강화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 9개 블로그 About 페이지 AI 생성 + WP 발행 (각 언어별 현지화)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 9개 블로그 Contact 페이지 AI 생성 + WP 발행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Phase 3 &amp;mdash; 기술적 SEO&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 이미지 width/height/lazy-loading 자동 삽입 (CLS 개선)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 이미지 파일명 키워드 기반 슬러그로 변경 (이미지 검색 노출)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ Organization JSON-LD + Breadcrumb 스키마 전체 활성화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;☑ LiteSpeed CSS 최소화 이미 활성화 확인&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Phase 4 &amp;mdash; 발행 파이프라인 검증&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 발행 전 SEO 자동 검증 로직 추가 (메타 설명 길이, 키워드 배치)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 포스트 발행 수 3개/일로 증가 (콘텐츠 축적 가속)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;전수검사 최종 결과&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;홈페이지 타이틀, 메타 설명, OG/Twitter Card, Canonical URL, hreflang, JSON-LD, About/Contact 페이지 등 &lt;b&gt;140/140 항목 전부 PASS&lt;/b&gt;. 사이트맵 총 463개 URL 포함.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 11: Indexing API --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;INDEXING&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;16. Google Indexing API로 색인 가속화&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;SEO 최적화를 끝낸 후, Google이 새로 개선된 메타데이터를 다시 크롤링하기를 기다리는 건 비효율적입니다. Google Indexing API를 사용하면 URL을 직접 제출해 색인을 촉진할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;색인 요청 자동화 스크립트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 자동 수집&lt;/b&gt; &amp;mdash; 10개 서버에 SSH로 접속해 WordPress REST API로 발행된 포스트 URL 수집&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일괄 제출&lt;/b&gt; &amp;mdash; Google Indexing API로 URL_UPDATED 타입으로 제출&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쿼타 관리&lt;/b&gt; &amp;mdash; 하루 200개 쿼타 초과 시 나머지 URL을 파일로 저장, 다음 실행 시 이어서 처리&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt; &amp;mdash; 10개 블로그 전체 388개 URL 색인 요청 완료&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;색인 요청 스크립트 사용법&lt;/p&gt;
&lt;pre class=&quot;vim&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# 전체 블로그 URL 수집 + 일괄 제출
python scripts/request_indexing.py

# 쿼타 초과로 남은 URL만 이어서 제출
python scripts/request_indexing.py --remaining

# 특정 URL 파일 지정
python scripts/request_indexing.py --file /tmp/urls.txt&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Google Sandbox 효과&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;신규 도메인은 일반적으로 3~6개월간 Google이 검색 순위를 억제합니다(Sandbox 효과). 현재 블로그 개설 1~2개월차이므로 낮은 노출 자체는 예상 범위입니다. SEO 최적화 + 색인 가속화 + 콘텐츠 축적을 병행하면 Sandbox 종료 시점(약 3개월)에 급격한 노출 증가를 기대할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 12: 성과와 계획 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;RESULTS &amp;amp; FUTURE&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;17. 현재 성과와 앞으로의 계획&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;현재까지 만든 것들&lt;/h3&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 10개 언어 WordPress 블로그 운영 중&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ AI 콘텐츠 생성 &amp;rarr; 썸네일 &amp;rarr; Cloudflare R2 &amp;rarr; WP 발행 완전 자동화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ GitHub Actions 10개 서버 병렬 배포 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ FIFO 큐 기반 랜덤 시간 자동 발행 스케줄러 (3개/일/블로그)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 운영 리포트 시스템 (GA4 + GSC &amp;rarr; HTML 리포트)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ 10개 블로그 SEO 전수검사 140/140 PASS&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;☑ Google Indexing API 388개 URL 색인 요청&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;☑ Slack 헬스체크 알림 시스템&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;앞으로의 계획&lt;/h3&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #7C3AED; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;단기 (1개월)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콘텐츠 확충&lt;/b&gt; &amp;mdash; 현재 35~70개 포스트/블로그를 100개 이상으로&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CTR 최적화&lt;/b&gt; &amp;mdash; GSC 데이터로 노출 대비 클릭 0인 포스트 제목/메타 개선&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Google Analytics 전환 추적&lt;/b&gt; &amp;mdash; 실제 수익 연결 지표 설정&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #7C3AED; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;중기 (3개월)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Google Sandbox 탈출&lt;/b&gt; &amp;mdash; 3개월 지점에서 노출 급증 기대&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Google AdSense 심사 신청&lt;/b&gt; &amp;mdash; 충분한 콘텐츠 확보 후&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내부 링크 고도화&lt;/b&gt; &amp;mdash; 관련 포스트 간 크로스링크 자동화&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 배운 것&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;자동화는 &lt;b&gt;완벽한 시스템이 아니라 빠르게 반복할 수 있는 시스템&lt;/b&gt;을 목표로 해야 합니다. 처음부터 완벽할 수 없고, 운영하면서 버그를 발견하고 개선하는 사이클이 중요합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;AI 콘텐츠도 SEO 기반이 제대로 갖춰져 있지 않으면 아무리 좋은 글도 검색에 노출되지 않습니다. 기술적 SEO는 콘텐츠보다 먼저 완성되어야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 10: 브랜드 로고 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;BRAND IDENTITY&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;10. KWON 브랜드 로고 시스템&lt;/h2&gt;
&lt;p&gt;&lt;img style=&quot;width: 100%; border-radius: 12px;&quot; src=&quot;https://cdn.kwonputer.com/ko/ai-multilingual-blog-2026/04-brand-logos.png&quot; alt=&quot;KWON 10개 다국어 블로그 브랜드 로고 시스템 &amp;mdash; Pillow 자동 생성&quot; width=&quot;1536&quot; height=&quot;1024&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;10개 블로그를 운영하다 보니 각 블로그의 정체성이 필요했습니다. 디자이너에게 맡기면 비용이 발생하고, AI 이미지 생성도 일관성이 떨어집니다. 그래서 &lt;b&gt;Python Pillow로 직접 로고 생성 시스템을 구축&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;브랜드 컨셉 &amp;mdash; 텍스트에 의미를 담다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;모든 블로그 도메인이 &lt;b&gt;kwon + 언어 의미 단어&lt;/b&gt; 조합입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwonputer&lt;/b&gt; (ko) &amp;mdash; Kwon + Computer&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwonglish&lt;/b&gt; (en) &amp;mdash; Kwon + English&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwonnen&lt;/b&gt; (de) &amp;mdash; Kwon + K&amp;ouml;nnen (독일어: 능력)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwonteki&lt;/b&gt; (ja) &amp;mdash; Kwon + テキ (일본어: 적합한)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwontenu&lt;/b&gt; (fr) &amp;mdash; Kwon + Contenu (프랑스어: 콘텐츠)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwonsejo&lt;/b&gt; (es) &amp;mdash; Kwon + Consejo (스페인어: 조언)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwontudo&lt;/b&gt; (pt) &amp;mdash; Kwon + Tudo (포르투갈어: 모든 것)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwonnis&lt;/b&gt; (nl) &amp;mdash; Kwon + Kennis (네덜란드어: 지식)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwontento&lt;/b&gt; (it) &amp;mdash; Kwon + Contento (이탈리아어: 만족)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kwontrol&lt;/b&gt; (tr) &amp;mdash; Kwon + Kontrol (터키어: 통제)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #7C3AED; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;로고 생성 자동화 (generate_logos.py)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;워드마크 PNG&lt;/b&gt; &amp;mdash; 10개 블로그 &amp;times; 투명/흰색 배경 (Montserrat Black 폰트)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원형 기어 아이콘&lt;/b&gt; &amp;mdash; 10개 블로그 &amp;times; 5가지 사이즈 (16/32/64/128/256px)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사이트 대표 아이콘&lt;/b&gt; &amp;mdash; 1024&amp;times;1024 고해상도 (파비콘/SNS 공유용)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;폰트는 Montserrat TTF(Black, Medium)를 fonts/ 폴더에 번들링해서 서버 환경에 무관하게 동일한 결과물을 보장합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;로고 시스템의 실용적 효과&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;브랜드 로고가 생기면서 OG 이미지, 파비콘, 썸네일 워터마크에 일관된 시각적 정체성을 적용할 수 있게 됐습니다. SEO 전수검사 시 og:image 항목에 이 로고 이미지를 사용해 10개 블로그 모두 PASS 처리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 11: AI 소설 시스템 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;EXPERIMENT &amp;amp; PIVOT&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;11. AI 소설 자동 발행 시스템 &amp;mdash; 구현에서 폐기까지&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;솔직하게 말하면 이 섹션이 이 프로젝트에서 가장 많이 배운 부분입니다. &lt;b&gt;잘 만든 기능을 과감하게 버리는 결정&lt;/b&gt;에 관한 이야기입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;구현 &amp;mdash; 에피소드 연재 시스템&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 장편 소설을 에피소드 단위로 자동 생성하고, 매일 새 화를 발행하는 시스템을 만들었습니다. 웹소설 플랫폼처럼 &quot;다음 화&quot; 네비게이션 버튼도 달렸고, 이전 화 발행 시 &quot;다음 화 준비 중&quot; 표시도 자동으로 업데이트됐습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;구현된 기능&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에피소드 이전/다음 네비게이션 버튼&lt;/b&gt; &amp;mdash; 자동 삽입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에피소드 중복 발행 방지&lt;/b&gt; &amp;mdash; 화수 기반 dedup 로직&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이전 화 자동 업데이트&lt;/b&gt; &amp;mdash; 새 화 발행 시 이전 화의 &quot;준비 중&quot; 표시 &amp;rarr; 링크로 교체&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;episodes_per_day 설정&lt;/b&gt; &amp;mdash; 스케줄러와 통합, 하루 N화 자동 발행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;폐기 &amp;mdash; 왜 없앴나&lt;/h3&gt;
&lt;div style=&quot;background-color: #fef2f2; border-left: 4px solid #B91C1C; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #b91c1c; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;폐기 이유&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콘텐츠 방향성 불일치&lt;/b&gt; &amp;mdash; 기술 블로그와 소설 콘텐츠는 독자층이 완전히 다릅니다. SEO 관점에서 키워드 일관성을 해치고 도메인 토픽 집중도가 떨어집니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 소설의 품질 한계&lt;/b&gt; &amp;mdash; Claude가 생성하는 소설은 일관된 스토리라인 유지가 어렵습니다. 에피소드가 쌓일수록 앞 내용과의 연속성이 무너졌습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수익화 경로 불명확&lt;/b&gt; &amp;mdash; 소설 트래픽은 AdSense 단가가 낮고, 주력 기술 콘텐츠 대비 ROI가 떨어졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;결국 소설 시스템 전체를 코드베이스에서 완전히 제거했습니다. 관련 커밋이 feat(구현) &amp;rarr; fix(버그 수정 3개) &amp;rarr; chore(전체 삭제)로 이어집니다. 코드를 지운 건 아쉽지만, 명확한 이유가 있는 결정이었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;교훈&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;기능을 완성도 있게 구현했더라도, 전체 전략과 맞지 않으면 과감하게 제거해야 합니다. 코드베이스가 단순해지면 유지보수가 쉬워지고, 핵심 기능에 집중할 수 있게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 12: 운영 리포트 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ANALYTICS&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;12. 운영 리포트 시스템 &amp;mdash; GA4 + GSC &amp;rarr; HTML 자동 생성&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;10개 블로그의 상태를 수동으로 하나씩 확인하는 건 불가능합니다. GA4(Google Analytics 4)와 GSC(Google Search Console) 데이터를 자동으로 수집해서 HTML 리포트로 만드는 시스템을 구축했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;리포트 시스템 구성&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GA4 연동&lt;/b&gt; &amp;mdash; Google Analytics Data API v1으로 방문자 수, 세션, 이탈률 수집&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSC 연동&lt;/b&gt; &amp;mdash; Search Console API로 노출수, 클릭수, CTR, 평균 순위 수집&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스크린샷 자동화&lt;/b&gt; &amp;mdash; Playwright로 각 블로그 홈페이지 + 상위 포스트 스크린샷 캡처&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 내러티브&lt;/b&gt; &amp;mdash; 수집된 데이터를 Claude가 분석해 한국어 인사이트 텍스트 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTML 리포트 출력&lt;/b&gt; &amp;mdash; 티스토리에 바로 올릴 수 있는 포맷으로 생성&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;리포트 CLI 사용법&lt;/p&gt;
&lt;pre class=&quot;verilog&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# 전체 기간 리포트 생성
python -m src report generate

# 최근 7일 리포트
python -m src report generate --period 7

# 스크린샷 없이 빠르게 생성
python -m src report generate --skip-screenshots

# AI 내러티브 없이 (수치 데이터만)
python -m src report generate --skip-ai&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;리포트를 만든 덕분에 &lt;b&gt;10개 블로그 SEO 전수검사&lt;/b&gt;도 가능했습니다. GSC 데이터에서 &quot;309 노출 / 0 클릭&quot;이라는 이상 징후를 발견한 것도 이 리포트 시스템 덕분입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fef2f2; color: #b91c1c; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;버그&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;리포트 썸네일 생성 시 AI IMAGE 플레이스홀더 충돌&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;리포트 HTML을 생성할 때 일반 포스트용 이미지 삽입 로직이 실행되어 리포트 내 스크린샷 자리에 엉뚱한 AI 이미지가 들어가는 문제가 있었습니다. 리포트 생성 파이프라인을 일반 포스트 파이프라인과 완전히 분리해서 해결했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 13: 법적 페이지 + 마이그레이션 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;LEGAL &amp;amp; MIGRATION&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;13. 법적 페이지 + 마이그레이션 도구 6종&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;법적 페이지 자동 생성 CLI&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AdSense 심사, GDPR, 각국 개인정보보호법 준수를 위해 10개 블로그 모두에 법적 페이지가 필요합니다. 10개 언어로 각각 작성하면 작업량이 방대합니다. CLI 명령 하나로 자동 생성하도록 구현했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;법적 페이지 CLI&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# 개인정보처리방침 + 면책조항 + 쿠키정책 WP 발행
python -m src wp legal-pages

# 발행 전 HTML 미리보기
python -m src wp legal-pages --dry-run&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;각 페이지는 해당 언어권의 법률 환경에 맞게 현지화됩니다. 독일어 블로그는 DSGVO(독일 GDPR), 브라질 포르투갈어 블로그는 LGPD 기준으로 작성됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;마이그레이션 도구 6종&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;이미 발행된 수백 개의 포스트를 일괄 개선해야 할 때 사용하는 도구들입니다. 처음부터 완벽하게 만들 수 없기 때문에, 나중에 일괄 업그레이드할 수 있는 마이그레이션 도구를 함께 구축했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;마이그레이션 도구 목록&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;headings&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px;&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt;로 작성된 제목을 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px;&quot;&gt;&amp;lt;h2&amp;gt;/&amp;lt;h3&amp;gt;&lt;/code&gt;로 일괄 변환&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;alt-text&lt;/b&gt; &amp;mdash; 영문 alt 텍스트를 한국어/해당 언어로 일괄 변환&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;json-ld&lt;/b&gt; &amp;mdash; 기존 포스트에 JSON-LD 구조화 데이터 일괄 삽입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;seo&lt;/b&gt; &amp;mdash; 헤딩 + alt + JSON-LD + SEO 설명 통합 일괄 처리&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;wp-seo&lt;/b&gt; &amp;mdash; WordPress slug + Rank Math 메타데이터 일괄 마이그레이션&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;thumbnails&lt;/b&gt; &amp;mdash; 기존 포스트 썸네일 AI 재생성 + WP featured image 교체&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;안전한 마이그레이션 &amp;mdash; dry-run 지원&lt;/p&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# 실제 변경 전 미리보기
python -m src migrate all --dry-run

# 썸네일만 강제 재생성 (기존 이미지 덮어씀)
python -m src migrate thumbnails --force

# 특정 포스트만 처리
python -m src migrate thumbnails --post-id abc123&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 14: 서버 운영 도구 --&gt;
&lt;p style=&quot;color: #2563eb; font-size: 13px; letter-spacing: 3px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;SERVER OPS&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;14. 서버 운영 도구 &amp;mdash; sync.sh + LiteSpeed 자동화&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions로 코드 배포는 자동화했지만, 포스트 파일과 메타데이터는 서버별로 다르기 때문에 수동으로 관리해야 합니다. 이를 위한 운영 전용 쉘 스크립트를 만들었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #2563EB; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;scripts/sync.sh 명령 목록&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;posts&lt;/b&gt; &amp;mdash; 로컬에서 생성한 포스트 HTML 파일을 서버로 전송&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;metadata-push&lt;/b&gt; &amp;mdash; 로컬 메타데이터 JSON &amp;rarr; 서버로 동기화&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;metadata-pull&lt;/b&gt; &amp;mdash; 서버 메타데이터 &amp;rarr; 로컬로 가져오기&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;full&lt;/b&gt; &amp;mdash; 포스트 전송 + draft&amp;rarr;ready 승격 + 큐 등록 + 메타데이터 pull 한번에&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;promote&lt;/b&gt; &amp;mdash; 서버의 draft 포스트를 ready 상태로 일괄 승격&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;queue&lt;/b&gt; &amp;mdash; 승격된 포스트를 스케줄러 발행 큐에 등록&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;status&lt;/b&gt; &amp;mdash; 10개 서버 상태 한눈에 확인 (큐 크기, 다음 발행 시간 등)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;migrate-seo&lt;/b&gt; &amp;mdash; 모든 서버에 SEO 마이그레이션 일괄 실행&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;migrate-thumbnails&lt;/b&gt; &amp;mdash; 모든 서버의 썸네일 AI 재생성 + WP featured image 교체&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;LiteSpeed Cache 일괄 설치 자동화&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;10개 서버에 WordPress를 세팅하고 LiteSpeed Cache 플러그인을 수동으로 설치하는 건 반복 작업입니다. WP-CLI를 활용한 일괄 설치 스크립트로 자동화했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;LiteSpeed + 필수 플러그인 일괄 설치&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# WP-CLI로 플러그인 일괄 설치 (SSH 접속 후)
wp plugin install litespeed-cache --activate
wp plugin install seo-by-rank-math --activate
wp plugin install wordfence --activate

# LiteSpeed Cache 최적화 설정 적용
wp litespeed-option set cache-browser 1
wp litespeed-option set optm-css_min 1
wp litespeed-option set optm-js_min 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #dbeafe; border-left: 4px solid #1D4ED8; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #1e40af; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;wp_category_map.json 배포 제외 이슈&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;각 서버는 WordPress 카테고리 ID가 다릅니다. 처음에 wp_category_map.json을 코드베이스에 포함시켰다가 서버마다 카테고리 ID가 달라 포스트가 잘못된 카테고리에 등록되는 문제가 발생했습니다. 해당 파일을 deploy.yml 배포 제외 목록에 추가하고, 각 서버에서 &lt;code style=&quot;background-color: #f1f3f5; padding: 2px 8px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;wp sync-categories&lt;/code&gt;로 독립적으로 생성하도록 변경했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #2563eb; border-radius: 16px; padding: 32px 40px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #bfdbfe; font-size: 16px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;AI로 10개 언어 블로그를 자동화하는 여정을 처음부터 끝까지 공개했습니다. 아직 수익화까지는 갈 길이 멀지만, 인프라는 탄탄하게 구축됐습니다.&lt;/p&gt;
&lt;p style=&quot;color: #bfdbfe; font-size: 16px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;비슷한 프로젝트를 진행 중이거나 궁금한 점이 있으시면 댓글로 남겨주세요. 가능한 모든 질문에 답변 드리겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #93c5fd; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;이 블로그도 동일 시스템으로 운영되고 있습니다  &lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/마케팅 &amp;middot; 수익화</category>
      <category>1인개발</category>
      <category>AI블로그</category>
      <category>seo최적화방법</category>
      <category>다국어블로그운영</category>
      <category>블로그수익화</category>
      <category>블로그운영팁</category>
      <category>블로그자동화</category>
      <category>사이드프로젝트</category>
      <category>워드프레스자동화</category>
      <category>콘텐츠자동화</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/656</guid>
      <comments>https://kwonputer.tistory.com/656#entry656comment</comments>
      <pubDate>Tue, 24 Mar 2026 10:43:03 +0900</pubDate>
    </item>
    <item>
      <title>GA4 퍼널 분석으로 마케팅 전환율 0%의 원인을 찾은 이야기</title>
      <link>https://kwonputer.tistory.com/655</link>
      <description>&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 13px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;요 약&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 24px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GA4 퍼널 분석으로 전환율 0%의 원인을 찾은 이야기&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 16px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Facebook 광고로 노출 8,185회, 랜딩 289회를 달성했는데 회원가입은 0명. 추측 대신 GA4 퍼널 이벤트 30개를 심어 데이터로 병목을 찾고, 실제로 개선한 과정을 공유합니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 키워드: &lt;b&gt;GA4 퍼널 분석&lt;/b&gt;, &lt;b&gt;전환율 최적화&lt;/b&gt;, &lt;b&gt;Facebook 광고&lt;/b&gt;, &lt;b&gt;데이터 드리븐 마케팅&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px;&quot;&gt;
&lt;h2 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size26&quot;&gt;이 글의 순서&lt;/h2&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;1. 문제 상황 &amp;mdash; 트래픽은 오는데 전환이 0&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;2. 첫 번째 발견 &amp;mdash; 배포 누락이라는 기술적 실수&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;3. 추측 vs 데이터 &amp;mdash; 왜 전환이 0인가?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;4. GA4 퍼널 이벤트 설계와 구현&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;5. 퍼널 데이터가 말해준 진짜 병목&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;6. 데이터 기반 개선 작업&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;7. 실전에서 배운 교훈 5가지&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 1. 문제 상황 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;PROBLEM&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;트래픽은 오는데, 전환이 0이다&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;사이드프로젝트로 만든 운세 앱 Fortie를 런칭하고, Facebook 광고를 집행했습니다. 인도네시아, 태국, 한국을 타겟으로 7일간의 캠페인을 돌렸습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 24px;&quot; data-ke-size=&quot;size16&quot;&gt;광고 대시보드의 숫자는 나쁘지 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.kwonputer.com/ko/ga4_funnel/ads.jpg&quot; alt=&quot;Facebook Ads Manager 캠페인 성과 &amp;mdash; launch_id, launch_th, launch_kr 3개 캠페인 활동 중&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Facebook 광고 성과 (30일 기준)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;launch_id (인도네시아)&lt;/b&gt;: 노출 6,246 / 결과 172 / 지출 ₩2,141&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;launch_th (태국)&lt;/b&gt;: 노출 1,453 / 결과 90 / 지출 ₩1,955&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;launch_kr (한국)&lt;/b&gt;: 노출 486 / 결과 27 / 지출 ₩1,921&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총 랜딩&lt;/b&gt;: 289회 / &lt;b&gt;총 지출&lt;/b&gt;: ₩6,017&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #e03131; padding-bottom: 0;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;회원가입 전환&lt;/b&gt;: 0명&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;289명이 랜딩 페이지까지 왔는데, 회원가입은 단 한 명도 없었습니다. 관리자 대시보드를 열어봤더니 콘텐츠 이용 통계도 0건이었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff0f0; border-left: 4px solid #e03131; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #e03131; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 질문&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;사람들이 들어오는데, 왜 아무 일도 안 일어나는 거지?&quot;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 2. 배포 누락 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 01&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;첫 번째 발견 &amp;mdash; 배포 누락이라는 기술적 실수&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 확인한 건 기술적인 문제였습니다. 관리자 대시보드에 콘텐츠 이용 로그가 0건이라는 건, 사용자 문제가 아니라 시스템 문제일 가능성이 높았으니까요.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;문제&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;코드는 작성했는데, 서버에 배포가 안 되어 있었다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;콘텐츠 이용 로깅 코드(&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;content_usage_logs&lt;/code&gt;)를 3월 19일에 커밋했지만, 운영 서버에는 배포하지 않았습니다. 3일간 데이터가 유실된 겁니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;즉시 배포 후 데이터 수집 시작을 확인했습니다. 하지만 이건 전환율 0%의 근본 원인이 아니었습니다. 로깅이 안 됐을 뿐, 사용자가 실제로 콘텐츠를 이용했을 수도 있으니까요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff8e1; border-left: 4px solid #f57f17; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #f57f17; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;교훈&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 작성 &amp;ne; 배포 완료.&lt;/b&gt; CI/CD 자동화가 없으면 이런 실수는 반드시 반복됩니다. 커밋했다고 안심하지 마세요.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 3. 추측 vs 데이터 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 02&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;추측 vs 데이터 &amp;mdash; 왜 전환이 0인가?&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;배포 문제를 해결한 뒤에도 여전히 전환은 0이었습니다. 원인을 추측해봤습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;추측 가능한 원인 5가지&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt; 결과 페이지에서 CTA(회원가입 버튼)를 아예 못 본다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&lt;/b&gt; CTA를 봤는데 가입할 동기가 부족하다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.&lt;/b&gt; 로그인 페이지 UX가 혼란스럽다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.&lt;/b&gt; 인앱 브라우저(Facebook/Instagram)에서 기술적 문제가 있다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5.&lt;/b&gt; 결과만 보고 바로 이탈한다&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;다섯 가지 중 어떤 게 진짜 원인인지 알 수 없었습니다. 여기서 중요한 깨달음을 얻었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f0f3ff; border-left: 4px solid #667eea; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #667eea; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 포인트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;추측으로 고치면 안 된다. 데이터로 원인을 확인해야 한다.&quot;&lt;/b&gt; 5개 중 하나를 찍어서 고치면 나머지 4개가 여전히 문제일 수 있습니다. 어디서 사용자가 빠지는지 정확히 측정해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 4. GA4 퍼널 이벤트 설계 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 03&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;GA4 퍼널 이벤트 설계와 구현&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;기존 GA4 설정을 확인해보니, 기본 페이지뷰만 수집하고 있었습니다. 커스텀 이벤트는 0개. 사용자가 &quot;어디까지&quot; 진행했는지 전혀 알 수 없는 상태였습니다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.kwonputer.com/ko/ga4_funnel/report_overview.jpg&quot; alt=&quot;GA4 보고서 개요 &amp;mdash; 총 사용자 280명, 새 사용자 266명, 평균 참여시간 10초, 이벤트 1.6천&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;설계한 퍼널 흐름&lt;/h3&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;랜딩 페이지 방문&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폼 시작&lt;/b&gt; (첫 번째 입력 필드 클릭)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1 ~ 4 완료&lt;/b&gt; (4단계 폼)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폼 제출&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CTA 클릭&lt;/b&gt; (회원가입/로그인 유도)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인 페이지 진입&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가입 완료&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;구현: gtag 유틸리티&lt;/h3&gt;
&lt;div style=&quot;background-color: #e8f5e9; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #2e7d32; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;코드 설명&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;각 페이지에서 사용자 행동을 추적하는 GA4 이벤트 전송 유틸리티입니다. 총 30개 이벤트를 7개 페이지에 삽입했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 14px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// gtag.ts &amp;mdash; GA4 이벤트 전송 유틸리티
export const trackEvent = (
  eventName: string,
  params?: Record&amp;lt;string, any&amp;gt;
) =&amp;gt; {
  if (typeof window !== 'undefined' &amp;amp;&amp;amp; window.gtag) {
    window.gtag('event', eventName, params);
  }
};

// 사용 예시
trackEvent('compat_form_start', { step: 1 });
trackEvent('compat_form_submit');
trackEvent('compat_result_view');
trackEvent('compat_cta_click', { type: 'signup' });&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;이벤트를 심은 뒤 GA4 실시간 보고서에서 즉시 이벤트가 찍히는 걸 확인했습니다. 이제 2일만 기다리면 퍼널 데이터를 볼 수 있었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;퍼널 설정 실수와 수정&lt;/h3&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;실수&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;퍼널 시작점을 잘못 잡았다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;처음에 퍼널 1단계를 &quot;랜딩 페이지 방문&quot;으로 설정했습니다. 그런데 Facebook 광고가 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;/friend-or-lover&lt;/code&gt; 페이지로 직접 보내고 있었습니다. 사용자가 랜딩 페이지를 거치지 않으니, 2단계 이후가 전부 0으로 나온 겁니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;퍼널 시작점을 &quot;썸 판별기 시작&quot;(= 광고가 보내는 실제 페이지)으로 변경했습니다. &lt;b&gt;실제 사용자 경로를 따라야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 5. 퍼널 데이터가 말해준 병목 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 04&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;퍼널 데이터가 말해준 진짜 병목&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 24px;&quot; data-ke-size=&quot;size16&quot;&gt;2일간 데이터를 수집한 뒤, GA4 탐색 &amp;gt; 유입경로 탐색 보고서를 열었습니다. 결과는 충격적이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.kwonputer.com/ko/ga4_funnel/funnel.jpg&quot; alt=&quot;GA4 유입경로 탐색 보고서 &amp;mdash; 썸 판별기 시작 195명에서 CTA 클릭 0명까지의 퍼널 시각화&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;퍼널 데이터 (2일간)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;썸 판별기 시작&lt;/b&gt;　　195명 (100%)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #e03131; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr; &lt;b&gt;90.77% 이탈&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폼 제출&lt;/b&gt;　　　　　 18명 (9.23%)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #e03131; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr; &lt;b&gt;77.78% 이탈&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과 확인&lt;/b&gt;　　　　 4명 (2.05%)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #e03131; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;　　&amp;darr; &lt;b&gt;100% 이탈&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CTA 클릭&lt;/b&gt;　　　　 0명 (0%)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인 페이지&lt;/b&gt;　　 0명&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인 시도&lt;/b&gt;　　　 0명&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인 성공&lt;/b&gt;　　　 0명&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가입 완료&lt;/b&gt;　　　　 0명&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 24px;&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 두 가지 병목을 명확하게 보여줬습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e03131; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;병목 1: 폼 시작 &amp;rarr; 제출 (90.77% 이탈)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;195명이 시작했는데 18명만 제출. 4단계 폼이 너무 길거나, 입력 항목이 부담스러운 것. 모바일에서 특히 이탈이 심했을 가능성이 높습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e03131; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;병목 2: 결과 확인 &amp;rarr; CTA 클릭 (100% 이탈)&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;4명이 결과까지 봤는데 CTA를 단 한 명도 안 클릭. &lt;b&gt;결과 페이지에 CTA가 없거나, 있어도 눈에 안 띄거나, 클릭할 이유가 없다는 뜻입니다.&lt;/b&gt; 여기가 가장 시급한 병목이었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #f0f3ff; border-left: 4px solid #667eea; border-radius: 0 8px 8px 0; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; font-weight: bold; color: #667eea; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 포인트&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;100% 이탈 = 가장 큰 기회.&lt;/b&gt; 10명이 결과까지 봤다는 건 관심은 있다는 뜻입니다. 여기만 고치면 전환이 생기기 시작합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 6. 데이터 기반 개선 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 05&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;데이터 기반 개선 작업&lt;/h2&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 24px;&quot; data-ke-size=&quot;size16&quot;&gt;가장 시급한 병목 2(결과 &amp;rarr; CTA 100% 이탈)부터 공략했습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;결과 페이지 개선 3가지&lt;/h3&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;display: inline-block; background-color: #667eea; color: #fff; font-size: 14px; font-weight: bold; padding: 4px 12px; border-radius: 50%;&quot;&gt;1&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;다른 콘텐츠 추천 캐러셀 추가&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;결과 페이지 하단에 &quot;다른 운세도 확인해보세요&quot; 캐러셀을 추가했습니다. DB에서 다른 콘텐츠를 자동으로 불러와 롤링합니다. 사용자가 결과만 보고 나가는 대신, 서비스를 더 탐색하도록 유도합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;display: inline-block; background-color: #667eea; color: #fff; font-size: 14px; font-weight: bold; padding: 4px 12px; border-radius: 50%;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;하단 고정 플로팅 로그인 바&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;비로그인 사용자에게만 화면 하단에 고정 바를 노출합니다. &quot;로그인하고 매일 운세 받기&quot; 같은 메시지로 가입 동기를 제공합니다. 스크롤해도 항상 보이므로, CTA를 &quot;못 봐서&quot; 이탈하는 문제를 원천 차단합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;display: inline-block; background-color: #667eea; color: #fff; font-size: 14px; font-weight: bold; padding: 4px 12px; border-radius: 50%;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;모든 클릭에 GA4 이벤트 추가&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;새로 추가한 캐러셀과 플로팅 바의 모든 클릭에 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;discover_*&lt;/code&gt;, &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 13px; color: #495057;&quot;&gt;floating_login_bar&lt;/code&gt; 이벤트를 심었습니다. 개선 효과를 데이터로 검증하기 위해서입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-top: 8px; padding-bottom: 16px;&quot; data-ke-size=&quot;size23&quot;&gt;추가 발견: 다국어 문제&lt;/h3&gt;
&lt;p style=&quot;font-size: 16px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;퍼널을 분석하다 예상 못 한 문제를 하나 더 발견했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 13px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;문제&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 20px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;인도네시아/태국 사용자에게 한국어 결과를 보여주고 있었다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;글로벌 타겟 광고인데, 결과 페이지의 운세 텍스트가 한국어로만 표시되고 있었습니다. 브라우저 언어 감지가 없고, DB 번역 데이터도 누락되어 있었습니다. 인도네시아 사용자가 한국어 운세를 보고 이탈하는 건 당연한 결과였습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 14px; font-weight: bold; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;10개 언어 번역 데이터를 추가했습니다. 6개 에이전트를 병렬로 돌려 약 340개 엔트리를 번역하고, 브라우저 언어 자동 감지 로직을 구현했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 7. 교훈 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 13px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;LESSONS&lt;/p&gt;
&lt;h2 style=&quot;font-size: 26px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 24px;&quot; data-ke-size=&quot;size26&quot;&gt;실전에서 배운 교훈 5가지&lt;/h2&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;1. 추측 금지, 데이터로 말하기&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;퍼널 이벤트 없이는 어디서 사용자가 빠지는지 알 수 없습니다. &quot;아마 이 부분이 문제일 거야&quot;라는 추측으로 코드를 고치면, 진짜 원인은 그대로 남아 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;2. 퍼널 설계 시 실제 사용자 경로를 따라야&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;광고 트래픽은 홈페이지를 안 거칩니다. 랜딩 URL이 어디인지 확인하고, 그 페이지부터 퍼널을 시작해야 합니다. 이론적인 동선이 아닌 실제 동선을 추적하세요.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;3. 100% 이탈 지점 = 가장 큰 기회&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;결과까지 본 4명은 관심이 있는 사람들입니다. 이 지점의 이탈률만 개선해도 전환이 생기기 시작합니다. 가장 이탈률이 높은 곳이 가장 ROI가 높은 개선 포인트입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;4. 기술 문제도 퍼널에서 발견된다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;배포 누락, 다국어 미지원, DB 시딩 누락 같은 기술적 문제도 퍼널 데이터에서 드러났습니다. 마케팅 문제와 기술 문제를 동시에 잡을 수 있는 게 퍼널 분석의 강점입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;5. GA4 실시간 보고서로 즉시 검증&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;이벤트를 심고 GA4 실시간 보고서에서 바로 확인할 수 있습니다. 배포 후 &quot;이벤트가 제대로 찍히나?&quot; 불안해할 필요 없이, 직접 테스트하면서 실시간으로 검증하세요.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고 자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 14px; color: #667eea; text-decoration: none;&quot; href=&quot;https://support.google.com/analytics/answer/9267568&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GA4 유입경로 탐색 보고서 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 20px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 사이드프로젝트 Fortie를 운영하면서 실제로 겪은 마케팅 삽질기입니다. &quot;데이터 드리븐&quot;이라는 말이 거창하게 느껴질 수 있지만, 핵심은 단순합니다. 추측하지 말고, 측정하고, 고치세요.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;비슷한 상황을 겪고 계신 분들에게 도움이 되었으면 좋겠습니다. 질문이나 피드백은 댓글로 남겨주세요!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/마케팅 &amp;middot; 수익화</category>
      <category>CRO</category>
      <category>FACEBOOK광고</category>
      <category>GA4</category>
      <category>구글애널리틱스</category>
      <category>데이터드리븐</category>
      <category>마케팅</category>
      <category>사이드프로젝트</category>
      <category>웹분석</category>
      <category>전환율최적화</category>
      <category>퍼널분석</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/655</guid>
      <comments>https://kwonputer.tistory.com/655#entry655comment</comments>
      <pubDate>Sun, 22 Mar 2026 15:50:45 +0900</pubDate>
    </item>
    <item>
      <title>메타(인스타그램) 광고 처음 세팅하기 - 하루 1,500원으로 3개국 광고 시작한 과정</title>
      <link>https://kwonputer.tistory.com/654</link>
      <description>&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;MARKETING&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;메타(인스타그램) 광고 처음부터 세팅까지 - 하루 1,500원으로 3개국 광고&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;개인 앱 서비스를 메타 광고로 홍보한 실제 과정을 정리했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;비즈니스 계정 생성부터 캠페인 세팅, 다국어 광고 집행까지 스크린샷으로 보여드립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;배경&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;비즈니스 계정 + 결제&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;Facebook 페이지&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;캠페인 만들기&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;크리에이티브 설정&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;다국어 확장&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block;&quot;&gt;예산 전략&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: 배경 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 메타 광고를 시작했나&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;개인 프로젝트로 운세 서비스 &lt;b&gt;Fortie&lt;/b&gt;를 만들었습니다. 웹, Android, iOS 모두 지원하고, 14개 언어로 글로벌 서비스를 하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;서비스는 만들었는데, 사용자를 어떻게 모을지가 문제였습니다. SEO는 시간이 걸리고, 당장 유입을 만들려면 &lt;b&gt;유료 광고&lt;/b&gt;가 가장 빠른 방법이었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;예산은 넉넉하지 않았습니다. &lt;b&gt;하루 1,500원&lt;/b&gt;으로 시작해보기로 했습니다. 메타(Instagram/Facebook) 광고를 선택한 이유는 단순합니다. 운세/궁합 같은 콘텐츠는 Instagram에서 반응이 가장 좋기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;메타 광고는 처음이라 하나하나 찾아가면서 세팅했습니다. 그 과정을 스크린샷과 함께 정리합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: 비즈니스 계정 + 결제 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 1&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Meta Business Suite 가입 + 결제 수단 등록&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;메타 광고를 하려면 먼저 &lt;b&gt;비즈니스 계정&lt;/b&gt;이 필요합니다. &lt;a style=&quot;color: #667eea;&quot; href=&quot;https://business.facebook.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;business.facebook.com&lt;/a&gt;에서 Facebook 계정으로 로그인하면 됩니다.&lt;/p&gt;
&lt;!-- 이미지: 비즈니스 홈 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/789e1d3c-a564-4a14-9bc2-1857c1184275.jpg&quot; alt=&quot;Meta Business Suite 로그인 화면&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Meta Business Suite 시작 화면&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;가입 후 대시보드에 들어가면 &quot;광고 계정 없음&quot;, &quot;페이지 없음&quot; 상태입니다. 하나씩 만들어야 합니다.&lt;/p&gt;
&lt;!-- 이미지: 수트 대시보드 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/96142217-a325-4373-b1bd-f3b822b78d30.jpg&quot; alt=&quot;Meta Business Suite 대시보드 - 광고 계정 없음&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;처음 가입하면 이런 화면이 보입니다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;b&gt;결제 수단&lt;/b&gt;을 등록합니다. 청구 및 결제 메뉴에서 카드(신용카드/체크카드)를 추가하면 됩니다.&lt;/p&gt;
&lt;!-- 이미지: 결제수단 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/0497c24f-1fc3-4707-b170-b6bf84741460.jpg&quot; alt=&quot;Meta 결제 수단 등록 화면&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결제 수단 등록 화면&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;광고 관리자(Ads Manager)에 들어가면 결제 수단 확인과 전화번호 인증을 요구합니다. 둘 다 완료해야 광고를 만들 수 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 이미지: 광고 관리자 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/04d3980a-49b3-498f-a77c-1c075c676adc.jpg&quot; alt=&quot;광고 관리자 - 결제 수단 및 전화번호 인증 완료&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결제 수단 ✓ 전화번호 인증 ✓ 완료된 상태&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: 페이지 만들기 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 2&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Facebook 페이지 만들기&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;광고를 돌리려면 &lt;b&gt;Facebook 페이지&lt;/b&gt;가 필요합니다. 광고 관리자 안에서 만들면 오류가 나는 경우가 있어서, &lt;a style=&quot;color: #667eea;&quot; href=&quot;https://www.facebook.com/pages/create&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;facebook.com/pages/create&lt;/a&gt;에서 직접 만드는 게 안전합니다.&lt;/p&gt;
&lt;!-- 이미지: 페이지 만들기 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/30ef9191-471f-45c0-825a-764a41a070e0.jpg&quot; alt=&quot;Facebook 페이지 만들기 - Fortie&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;페이지 이름, 카테고리, 프로필 사진을 설정합니다&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;페이지 설정 팁&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;페이지 이름&lt;/b&gt; &amp;mdash; 브랜드명 하나만 쓰세요. 국가/콘텐츠명을 붙이지 마세요. 이 페이지 하나로 모든 국가 광고를 돌립니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카테고리&lt;/b&gt; &amp;mdash; &quot;앱&quot;, &quot;엔터테인먼트 웹사이트&quot;, &quot;점성술사&quot; 등에서 맞는 걸 선택. 최대 3개 가능합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로필 사진&lt;/b&gt; &amp;mdash; 앱 로고나 아이콘을 업로드하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: 캠페인 만들기 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 3&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;캠페인 만들기 - 목표, 예산, 타겟&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;광고 관리자에서 &quot;만들기&quot;를 누르면 캠페인을 설정할 수 있습니다.&lt;/p&gt;
&lt;!-- 이미지: 캠페인 설정 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/731f981d-3acc-4bd8-9472-fab24241c2f2.jpg&quot; alt=&quot;메타 광고 캠페인 설정 - 트래픽 목표&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;캠페인 목표: 트래픽 / 구매 유형: 경매 / 예산: 광고 세트 예산&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;캠페인 설정&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캠페인 목표&lt;/b&gt; &amp;mdash; &quot;트래픽&quot; 선택. 웹사이트 방문을 늘리는 게 목표입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구매 유형&lt;/b&gt; &amp;mdash; &quot;경매&quot; 그대로. 소액 예산에는 경매가 맞습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캠페인 이름&lt;/b&gt; &amp;mdash; 나중에 구분하기 쉽게 국가 코드를 넣습니다. 저는 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;launch_kr&lt;/code&gt;로 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 &lt;b&gt;광고 세트&lt;/b&gt; 설정입니다. 여기서 예산, 타겟(국가/연령/관심사), 게재위치를 정합니다.&lt;/p&gt;
&lt;!-- 이미지: 광고 세트 설정 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/abbd92b9-0906-4c7f-a4a8-013379e61835.jpg&quot; alt=&quot;메타 광고 세트 설정 - 예산, 타겟, 게재위치&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;광고 세트: 전환 위치, 성과 목표, 예산, 타겟&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;광고 세트 설정 (한국 기준)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일일 예산&lt;/b&gt; &amp;mdash; 1,500원 (메타 최소 요구 금액이 약 1,473원이라 1,500원으로 설정)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지역&lt;/b&gt; &amp;mdash; 대한민국&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연령&lt;/b&gt; &amp;mdash; 18~35세&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관심사&lt;/b&gt; &amp;mdash; Horoscope, Astrology, Fortune-telling 등 (검색해서 나오는 것만 추가)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: 광고 소재 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 4&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;광고 소재 - 이미지, 문구, 링크&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;광고에 보여줄 이미지와 텍스트를 설정합니다. Facebook 페이지를 선택하고, 이미지를 업로드하고, 문구를 입력합니다.&lt;/p&gt;
&lt;!-- 이미지: 광고 설정 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/b470c696-f4b6-432d-8154-14c153c8ec2c.jpg&quot; alt=&quot;메타 광고 소재 설정 - 페이지 선택, 이미지, 문구&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Facebook 페이지 선택 + 이미지 업로드 + 미리보기&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;크리에이티브 설정&quot;에서 이미지 업로드, 자르기, 문구 입력을 합니다.&lt;/p&gt;
&lt;!-- 이미지: 크리에이티브 설정 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/c413faec-e81e-4ae1-acb5-a55bba710f8c.jpg&quot; alt=&quot;크리에이티브 설정 - URL, 사이트 링크&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;크리에이티브 설정: URL + 확장 기능&lt;/p&gt;
&lt;!-- 이미지: 문구 설정 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/1401dbde-eb8a-4202-90e4-3e808b13cc4e.jpg&quot; alt=&quot;메타 광고 문구 설정 - 기본 문구, 제목, 설명 옵션&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기본 문구 4개, 제목 3개, 설명 3개 &amp;mdash; 메타가 반응 좋은 조합을 자동으로 선택합니다&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;문구 팁&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 문구&lt;/b&gt; &amp;mdash; 여러 버전을 넣으세요. 메타가 A/B 테스트해서 반응 좋은 걸 자동으로 더 많이 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지&lt;/b&gt; &amp;mdash; 텍스트를 넣지 마세요. 메타는 이미지 내 텍스트가 20% 이상이면 노출이 줄어듭니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UTM 링크&lt;/b&gt; &amp;mdash; 웹사이트 URL에 UTM 파라미터를 붙여서 GA4에서 광고 유입을 추적할 수 있게 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 6: 게시 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;STEP 5&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;게시 - 광고 시작&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;모든 설정이 끝나면 &quot;동의하고 게시&quot;를 누릅니다. 메타에서 검토 후 보통 몇 시간 내에 광고가 시작됩니다.&lt;/p&gt;
&lt;!-- 이미지: 게시 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/dc69133d-671c-476c-999e-0cfc4a86477a.jpg&quot; alt=&quot;메타 광고 게시 - 3/3개 게시 중&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;게시 완료 &amp;mdash; 검토 후 &quot;활동 중&quot;으로 바뀌면 광고가 시작됩니다&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 7: 다국어 확장 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;GLOBAL&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;다국어 광고 - 태국, 인도네시아 추가&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;한국 캠페인을 만든 것과 같은 방법으로, 태국과 인도네시아 캠페인도 추가했습니다. 같은 이미지를 사용하되, &lt;b&gt;문구는 각 나라 언어&lt;/b&gt;로, &lt;b&gt;타겟 지역/연령/관심사&lt;/b&gt;만 변경했습니다.&lt;/p&gt;
&lt;!-- 이미지: 태국 캠페인 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/326887e8-004c-409d-a902-97cecc5b1108.jpg&quot; alt=&quot;메타 광고 태국 캠페인 - launch_th&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;태국 캠페인 (launch_th) &amp;mdash; 타겟: 태국, 20~35세&lt;/p&gt;
&lt;!-- 이미지: 인도네시아 캠페인 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/9157b130-1ba6-4bb4-9283-9ca9d102c3dc.jpg&quot; alt=&quot;메타 광고 인도네시아 캠페인 - launch_id&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;인도네시아 캠페인 (launch_id) &amp;mdash; 타겟: 인도네시아, 18~35세&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;태국 광고 주의사항&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;태국은 법적으로 &lt;b&gt;20세 미만에게 광고를 보여줄 수 없습니다&lt;/b&gt;. 연령을 18세로 설정하면 오류가 나므로, 태국 캠페인은 20~35세로 설정해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 이미지: 최종 완료 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/meta-ads/bd945e6c-5dd1-4126-b542-28d729f575fc.jpg&quot; alt=&quot;메타 광고 3개국 캠페인 완료 - launch_kr, launch_th, launch_id&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;3개국 캠페인 모두 &quot;활동 중&quot; &amp;mdash; 한국, 태국, 인도네시아&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 8: 예산 전략 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;BUDGET&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;하루 4,500원, 3개국 광고의 예산 전략&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;하루 예산이 1,500원 x 3개국 = &lt;b&gt;총 4,500원&lt;/b&gt;입니다. 같은 돈이라도 어느 나라에 쓰느냐에 따라 결과가 크게 달라집니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;같은 1,500원인데 결과가 다릅니다&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국&lt;/b&gt;: 클릭 1회에 300~700원 &amp;rarr; 하루 2~5명 유입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;태국&lt;/b&gt;: 클릭 1회에 30~80원 &amp;rarr; 하루 20~50명 유입&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인도네시아&lt;/b&gt;: 클릭 1회에 30~80원 &amp;rarr; 하루 20~50명 유입&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;한국은 CPC(클릭당 비용)가 비싸서 소수만 유입되지만, 사주 문화가 있어서 충성 유저가 될 가능성이 높습니다. 태국과 인도네시아는 CPC가 매우 저렴하고, 점술/운세 문화가 일상적이라 가성비가 좋습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;2주 정도 돌려보고, GA4에서 국가별 참여율/이탈률을 확인한 후 예산을 재배분할 계획입니다. 영어권(미국/영국)은 CPC가 500~1,500원이라 지금 예산으로는 비효율적이므로, 나중에 예산이 늘면 진행할 예정입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;정리&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;메타 광고는 처음이라 헤맸지만, 한번 세팅해놓으니 크게 어렵지 않았습니다. 비즈니스 계정 만들기 &amp;rarr; 결제 등록 &amp;rarr; 페이지 만들기 &amp;rarr; 캠페인 만들기 &amp;rarr; 소재 설정 &amp;rarr; 게시. 이 순서대로 하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;하루 1,500원이면 한 달에 약 45,000원입니다. 커피 몇 잔 가격으로 인스타그램에 광고를 돌릴 수 있다는 게 신기했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 광고 성과 데이터(클릭수, CPC, 국가별 비교)를 공유할 예정입니다. 메타 광고를 처음 시작하시는 분들에게 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;메타 광고 처음이신 분들은 이 글을 보면서 따라하시면 됩니다. 하루 1,500원부터 시작할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;궁금한 점이 있으면 댓글로 남겨주세요!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/마케팅 &amp;middot; 수익화</category>
      <category>1인개발</category>
      <category>FACEBOOK광고</category>
      <category>UTM추적</category>
      <category>광고세팅</category>
      <category>다국어광고</category>
      <category>메타광고</category>
      <category>소액광고</category>
      <category>앱마케팅</category>
      <category>인디개발자</category>
      <category>인스타그램광고</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/654</guid>
      <comments>https://kwonputer.tistory.com/654#entry654comment</comments>
      <pubDate>Wed, 18 Mar 2026 11:48:58 +0900</pubDate>
    </item>
    <item>
      <title>[풀스택] 크라우드소싱 운세 플랫폼 'Fortie' 개발기 - NestJS + Next.js + Flutter + GPT 파인튜닝</title>
      <link>https://kwonputer.tistory.com/653</link>
      <description>&lt;!-- 스토어 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie (포티) 다운로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #667eea; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://myfortie.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Web&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kfortune&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/app/id6758990823&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie (포티) - 크라우드소싱 운세 플랫폼 풀스택 개발기&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;나와 같은 사주의 사람들은 오늘 어떤 하루를 보냈을까?&quot; - 기록할수록 정확해지는 개인화 운세 서비스입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NestJS + PostgreSQL + Next.js 14 + Flutter&lt;/b&gt; 풀스택 구성.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사주 + 점성술 2엔진&lt;/b&gt;, &lt;b&gt;GPT-4o-mini 파인튜닝&lt;/b&gt;, &lt;b&gt;관리자 대시보드&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;결정론적 엔진 설계&lt;/b&gt;, &lt;b&gt;AI 파인튜닝 파이프라인&lt;/b&gt;, &lt;b&gt;개인 정확도 시스템&lt;/b&gt;, &lt;b&gt;하이브리드 WebView 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 이 서비스를 만들었나?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;서비스 핵심 철학&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;전체 아키텍처&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;백엔드 (NestJS)&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;웹 프론트엔드 (Next.js)&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;모바일 (Flutter)&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;관리자 대시보드&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;AI 파인튜닝&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술 스택&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 1: WHY --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이 서비스를 만들었나?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;기존 운세 앱들의 한계가 명확했습니다. 대부분 &lt;b&gt;&quot;오늘의 운세&quot;를 일방적으로 보여주기만&lt;/b&gt; 할 뿐, 그 운세가 실제로 맞았는지 검증하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie는 질문 하나에서 시작했습니다: &lt;b&gt;&quot;나와 같은 사주의 사람들은 오늘 어떤 하루를 보냈을까?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 피드백(오늘 하루 기록)을 수집하고, 같은 사주를 가진 사람들의 통계를 쌓아서, &lt;b&gt;기록할수록 정확해지는 운세&lt;/b&gt;를 만들고 싶었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로는 &lt;b&gt;사주(동양 역학) + 점성술(Swiss Ephemeris) 2개 엔진&lt;/b&gt;의 수학적 계산 결과를, &lt;b&gt;GPT-4o-mini 파인튜닝 모델&lt;/b&gt;로 해석하는 구조를 설계했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;그 결과, 백엔드 + 웹 프론트 + 모바일 + 관리자 대시보드로 구성된 &lt;b&gt;풀스택 운세 플랫폼&lt;/b&gt;이 탄생했습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 2: 서비스 핵심 철학 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;PHILOSOPHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;서비스 핵심 철학&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;주관 0%, 결정론적&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;같은 생년월일+시간을 입력하면 &lt;b&gt;항상 같은 사주/점성술 결과&lt;/b&gt;를 반환합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 하는 일은 수학적 계산 결과를 &lt;b&gt;사람이 읽기 쉬운 문장&lt;/b&gt;으로 번역하는 것뿐입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;부정도 가치다&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;다른 운세 앱은 긍정적인 내용만 보여줍니다. Fortie는 &lt;b&gt;긍정/부정/중립 모두 있는 그대로&lt;/b&gt; 표현합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;오늘 조심해야 할 것&quot;을 아는 것이 &quot;오늘 좋은 일&quot;만 듣는 것보다 가치 있다고 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;크라우드소싱 데이터 플라이휠&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 매일 피드백(하루 기록)을 남기면, 같은 사주를 가진 사람들의 &lt;b&gt;실제 경험 통계&lt;/b&gt;가 쌓입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;기록이 많을수록 개인 정확도가 올라가고, 7일/30일/90일/180일 마일스톤마다 &lt;b&gt;새로운 기능이 해금&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 3: 전체 아키텍처 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;전체 아키텍처&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;3개 레포지토리 구성&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;fortune-api (백엔드 + 관리자 대시보드)
  NestJS 11 + TypeScript + PostgreSQL 16 + TypeORM
  관리자 웹: React 19 + Vite + TailwindCSS 4
  AI: GPT-4o-mini 파인튜닝
  다국어: 14개 언어 지원

fortune-web (웹 프론트엔드)
  Next.js 14 (App Router) + TypeScript
  Zustand + TanStack Query v5
  커스텀 글래스모피즘 SVG + 5탭 네비게이션
  14개 언어 다국어 지원

fortune-app (모바일 앱)
  Flutter + Dart (하이브리드 WebView 셸)
  네이티브: 소셜 로그인 + FCM + Remote Config
  콘텐츠: WebView로 fortune-web 로드
  iOS + Android&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 흐름&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자 입력&lt;/b&gt; (생년월일/시간/성별) &amp;rarr; &lt;b&gt;2개 엔진&lt;/b&gt; (사주 계산 + Swiss Ephemeris 점성술)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔진 결과&lt;/b&gt; (4주, 십성, 행성 위치, 트랜짓) &amp;rarr; &lt;b&gt;GPT-4o-mini 파인튜닝&lt;/b&gt; (자연어 해석)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 해석&lt;/b&gt; &amp;rarr; &lt;b&gt;웹/앱 UI&lt;/b&gt; (글래스모피즘 카드, 커스텀 차트)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자 피드백&lt;/b&gt; (하루 기록) &amp;rarr; &lt;b&gt;크라우드 통계&lt;/b&gt; + &lt;b&gt;개인 정확도&lt;/b&gt; &amp;rarr; 다음 날 운세에 반영&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 4: 백엔드 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;BACKEND&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 (NestJS + PostgreSQL)&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;2개 계산 엔진&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사주 엔진 (Saju)&lt;/b&gt; &amp;mdash; 60갑자 기반 4주(년주/월주/일주/시주) 계산, 충/합/형, 십성, 12지지, 신살, 신강/약, 용신, 납음까지 전통 한국 역학 체계 전체 구현&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;점성술 엔진 (Stellar)&lt;/b&gt; &amp;mdash; Swiss Ephemeris(swisseph 0.5.17) 기반 실시간 행성 위치 계산, 진트랜짓, 하우스 시스템, 달 위상, 행성 간 어스펙트 분석&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 해석 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPT-4o-mini 파인튜닝&lt;/b&gt; &amp;mdash; 언어별 개별 파인튜닝 모델 운용. Claude Sonnet으로 정답지(Ground Truth) 생성 &amp;rarr; GPT-4o-mini에 학습&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프롬프트 템플릿 시스템&lt;/b&gt; &amp;mdash; 엔진 데이터 + 사용자 프로필 메타데이터를 주입하는 구조화된 프롬프트&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;규칙 기반 폴백&lt;/b&gt; &amp;mdash; LLM 실패 시 규칙 기반(Rule-based) 해석으로 자동 전환, 서비스 중단 없음&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;인증 + 인프라&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소셜 로그인&lt;/b&gt; &amp;mdash; Google OAuth + Apple Sign In + 게스트 로그인(디바이스 ID), JWT + Passport 기반&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL 16 + TypeORM&lt;/b&gt; &amp;mdash; 마이그레이션 기반 스키마 관리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cloudflare R2&lt;/b&gt; &amp;mdash; 이미지/미디어 CDN 스토리지 (cdn.myfortie.com)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker 멀티스테이지 빌드&lt;/b&gt; &amp;mdash; builder &amp;rarr; production 이미지 분리 배포&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;모듈 구조&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;src/
├── admin/          관리자 API + R2 업로드
├── auth/           소셜 로그인 (Google/Apple/Guest) + JWT
├── fortune/        포티 조회 (2개 엔진 + 군중 통계 + 개인화)
├── feedback/       피드백 V2.2 (9카테고리 + 감정 기반)
├── personal/       개인 정확도 (패턴 감지, 점수, 마일스톤)
├── counsel/        Fortie에게 물어보기 (7턴 상담, 파인튜닝)
├── daily-prediction/ 내일 예측 (LLM + 규칙 폴백)
├── fortune-flow/   운세 흐름 (4카테고리 3개월 분석)
├── fantasy/        판타지 전생 직업 (20개 타입, 바이럴)
├── compatibility/  썸 판별기 v4.0 (100조합, 6등급)
├── historical/     역사 콘텐츠 &quot;나와 똑같은 날&quot; (2,285건)
├── subjective/     관점별 주관 해석 (7개 관점)
├── cms/            CMS 7종 (이벤트/공지/FAQ/법적/배너/팝업/문의)
├── engines/        사주 + 점성술 엔진 코어
├── interpretation/ AI 해석 (LLM 파인튜닝 모델)
├── notification/   FCM 푸시 + 알림함
├── analysis/       데이터 분석 (Chi-square, Lift)
└── common/         공통 (가드, 유틸, 엔티티)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 5: 웹 프론트엔드 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WEB FRONTEND&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;웹 프론트엔드 (Next.js 14)&lt;/p&gt;
&lt;!-- 모바일 이미지 2: 홈 화면 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 20px;&quot;&gt;&lt;img style=&quot;max-width: 320px; border-radius: 16px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/4e582611-abc3-4e45-98d0-3177b920120e.png&quot; alt=&quot;Fortie 홈 화면 - AI 메시지와 오늘의 조언&quot; /&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; padding-top: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;홈 화면 - Fortie가 전하는 메시지 + 오늘의 조언 (DO/DON'T/TIP)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;5탭 네비게이션 구조&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/home (오늘)&lt;/b&gt; &amp;mdash; AI 인사이트 카드, 오늘의 조언(DO/DON'T/TIP), 크라우드 통계, 역사 콘텐츠, 피드백 CTA&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/analysis (분석)&lt;/b&gt; &amp;mdash; 사주/스텔라 2탭, 인사이트 카드 그리드, 카드별 상세 뷰&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/calendar (기록)&lt;/b&gt; &amp;mdash; 월간 캘린더 히트맵, 카테고리별 통계, 피드백 타임라인&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/contents (콘텐츠)&lt;/b&gt; &amp;mdash; 판타지 전생 직업, 썸 판별기, Fortune Flow, AI 상담, 내일 예측&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/profile (마이)&lt;/b&gt; &amp;mdash; 12지신/별자리 아바타, 프로필 편집, 알림함, 언어 변경&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 모바일 이미지 5+6: 분석 탭 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 280px; border-radius: 16px; border: 1px solid #e9ecef; display: inline-block; margin: 4px;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/e63213b7-2c22-44df-8dd4-7fc51b7611bb.png&quot; alt=&quot;Fortie 분석 - 사주 탭&quot; /&gt; &lt;img style=&quot;max-width: 280px; border-radius: 16px; border: 1px solid #e9ecef; display: inline-block; margin: 4px;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/6e6efa4d-c5fc-4b99-9745-6729ad623562.png&quot; alt=&quot;Fortie 분석 - 스텔라 탭&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;분석 탭 - 사주(좌) / 스텔라(우) 인사이트 카드 그리드&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;글래스모피즘 SVG 디자인 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 SVG 아이콘&lt;/b&gt; &amp;mdash; 반투명 레이어 + 그라디언트 배경의 글래스모피즘 아이콘. 히어로, 카드, 행성, 별자리, 동물, 메타포 등 전체 직접 제작&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 차트&lt;/b&gt; &amp;mdash; ArcGauge, DonutChart, NatalChart(천궁도), WuxingBars(오행), EnergyMeter, CompassDiagram 등 전부 SVG로 직접 구현 (차트 라이브러리 미사용)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;절대 규칙&lt;/b&gt; &amp;mdash; 이모지 사용 금지, 모든 아이콘은 커스텀 SVG, 가라데이터/폴백 금지&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;상태 관리 전략&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Zustand&lt;/b&gt; &amp;mdash; 클라이언트 상태 (인증, UI 모달, 언어 설정). localStorage persist로 새로고침에도 유지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TanStack Query v5&lt;/b&gt; &amp;mdash; 서버 상태 (운세 데이터, 피드백, 알림). 자동 캐싱, stale-while-revalidate&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Axios 인터셉터&lt;/b&gt; &amp;mdash; JWT 자동 주입, snake_case &amp;harr; camelCase 자동 변환, Accept-Language 동적 설정, 401 자동 로그아웃&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 6: 모바일 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;MOBILE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;모바일 앱 (Flutter - 하이브리드 WebView)&lt;/p&gt;
&lt;!-- 모바일 이미지 1: 썸 판별기 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 20px;&quot;&gt;&lt;img style=&quot;max-width: 320px; border-radius: 16px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/83198dbf-458d-47e2-a1a3-19dd5356c2bd.png&quot; alt=&quot;Fortie 모바일 - 썸 판별기 배너&quot; /&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; padding-top: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;모바일 앱 - 홈 배너에서 '썸 판별기' 콘텐츠 노출&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie 모바일은 &lt;b&gt;네이티브 + WebView 하이브리드&lt;/b&gt; 구조입니다. 로그인/알림/강제업데이트 같은 OS 종속 기능은 네이티브(Flutter)로, 콘텐츠 UI는 WebView로 fortune-web을 로드합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;네이티브 영역 (Flutter/Dart)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소셜 로그인 4종&lt;/b&gt; &amp;mdash; Google OAuth, Apple Sign In, 게스트(디바이스 ID), 리뷰어 계정&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCM 푸시 알림&lt;/b&gt; &amp;mdash; 알림 권한 요청, 토큰 등록, 포그라운드 로컬 알림, 백그라운드 핸들러&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Remote Config&lt;/b&gt; &amp;mdash; 강제 업데이트(min_version), 서버 점검 모드, 인앱 공지사항&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;WebView 연동 (InAppWebView)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Auth 주입&lt;/b&gt; &amp;mdash; 로그인 후 Zustand 포맷 JSON을 localStorage에 AT_DOCUMENT_START로 주입. 기존 언어 설정 덮어쓰기 방지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JS &amp;harr; Native 브릿지&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;window.KFortune.onLogout()&lt;/code&gt; / &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;window.KFortune.onAuthChange(token)&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URL 감지&lt;/b&gt; &amp;mdash; WebView URL이 /login 포함 시 자동으로 네이티브 LoginPage로 전환&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;앱 플로우&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;main() &amp;rarr; Firebase 초기화 + FCM 등록 + GA
  &amp;darr;
SplashPage (1.2s 페이드인)
  &amp;darr;
Remote Config 체크
  &amp;rarr; 강제업데이트? &amp;rarr; 스토어 이동
  &amp;rarr; 서버 점검? &amp;rarr; 점검 메시지
  &amp;rarr; 공지사항? &amp;rarr; 공지 모달
  &amp;darr;
저장된 토큰 검증 (/auth/profile)
  &amp;rarr; 유효 &amp;rarr; WebViewPage (myfortie.com/home)
  &amp;rarr; 무효 &amp;rarr; LoginPage (4가지 로그인)
  &amp;darr;
WebViewPage
  &amp;rarr; AT_DOCUMENT_START에 auth JSON 주입
  &amp;rarr; JS &amp;harr; Native 핸들러 등록
  &amp;rarr; FCM 토큰 자동 등록
  &amp;rarr; /login 감지 시 네이티브 전환&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 7: 관리자 대시보드 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ADMIN&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 대시보드 (React 19 + Vite)&lt;/p&gt;
&lt;!-- 관리자 이미지 0: 상담 테스트 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 20px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/28209777-ba78-43ed-9a1e-14b75e4db899.jpg&quot; alt=&quot;Fortie Admin - AI 상담 테스트 페이지&quot; /&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; padding-top: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 - AI 상담 테스트 (GPT-4o-mini vs Claude 정답지 비교, 사주 데이터 기반)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;관리 페이지 구성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대시보드&lt;/b&gt; &amp;mdash; KPI + 감정 비율 바 + 차트. 전체 서비스 현황 한눈에 파악&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 테스트&lt;/b&gt; &amp;mdash; 상담 테스트(7턴 시뮬레이션), 예측 테스트, 프롬프트 테스트(사주/점성술 계산 + AI 해석 비교)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콘텐츠 관리&lt;/b&gt; &amp;mdash; 텍스트 DB CRUD + JSON 내보내기/가져오기, 질문 트리 시각화, 콘텐츠 카드 관리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CMS&lt;/b&gt; &amp;mdash; 이벤트, 공지사항, FAQ, 법적 문서, 배너, 팝업, 문의 (TipTap 에디터)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;운영&lt;/b&gt; &amp;mdash; 사용자 관리, 피드백 브라우저, 푸시 알림 발송, 앱 버전 설정, 탈퇴 사유 통계&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 관리자 이미지 1+2: 배너 + 콘텐츠 카드 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/05a0a0d1-1c59-47cf-b179-c6451df59b7f.jpg&quot; alt=&quot;Fortie Admin - 배너 관리&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 - 배너 관리 (홈 캐러셀 배너 CRUD)&lt;/p&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 8px;&quot;&gt;&lt;img style=&quot;max-width: 100%; border-radius: 12px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/dff1ae1f-2d03-4dda-9a3a-55338fa62e20.jpg&quot; alt=&quot;Fortie Admin - 콘텐츠 카드 관리&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; text-align: center; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 - 콘텐츠 카드 관리 (slug/경로/태그/뱃지/활성화 등)&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 기술 스택&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React 19 + Vite&lt;/b&gt; &amp;mdash; 빠른 빌드, React Router v7 기반 라우팅&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Zustand + TanStack Query&lt;/b&gt; &amp;mdash; 프론트엔드와 동일한 상태 관리 패턴&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Recharts&lt;/b&gt; &amp;mdash; KPI 대시보드 차트/그래프 시각화&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TipTap 에디터&lt;/b&gt; &amp;mdash; CMS 콘텐츠 Markdown/HTML 편집. S3 + CloudFront 배포&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 8: 주요 기능 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능&lt;/p&gt;
&lt;!-- 기능 1: 오늘의 운세 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;1. 오늘의 운세 (/fortune/today)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2개 엔진 통합 응답&lt;/b&gt; &amp;mdash; 사주 정보(4주, 십성, 충/합, 신살, 용신, 납음) + 점성술(행성 위치, 트랜짓, 하우스, 달 위상)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 해석&lt;/b&gt; &amp;mdash; 핵심 요약, 행동 팁(해야 할 것/하지 말 것), 메타포 카드(사주 + 점성)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;군중 통계&lt;/b&gt; &amp;mdash; 같은 사주의 사람들이 경험한 카테고리별 통계. 개인 vs 전체 비교&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 2: 피드백 시스템 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;2. 3단계 피드백 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원탭 (~3초)&lt;/b&gt; &amp;mdash; 이모지 mood 1개로 빠른 기록. 기본 운세 잠금해제&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;간단 (~30초)&lt;/b&gt; &amp;mdash; mood + 카테고리 1~3개 선택&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상세 (~2분)&lt;/b&gt; &amp;mdash; mood + 카테고리 + impact + 시간대 + 자유 텍스트&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카테고리&lt;/b&gt; &amp;mdash; 재물, 연애, 커리어, 건강, 대인관계, 감정, 창의력, 학습, 신체 등&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 3: 개인 정확도 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;3. 개인 정확도 + 마일스톤 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FIRST_PATTERN (7일)&lt;/b&gt; &amp;mdash; 첫 개인 패턴 감지 (예: &quot;충이 있는 날 변화 경험 43%&quot;)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CROWD_COMPARE (30일)&lt;/b&gt; &amp;mdash; 개인 vs 전체 통계 비교&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DEEP_PROFILE (90일)&lt;/b&gt; &amp;mdash; 최고/최저 패턴 분석, 엔진별 맞춤도&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PERSONAL_INSIGHTS (180일)&lt;/b&gt; &amp;mdash; 운세에 &quot;나의 패턴&quot;이 반영되기 시작&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 모바일 이미지 4: 캘린더 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 20px;&quot;&gt;&lt;img style=&quot;max-width: 320px; border-radius: 16px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/bb3e28d5-42f3-4fe8-97b6-dfb44a48b6ec.png&quot; alt=&quot;Fortie 기록 - 월간 캘린더&quot; /&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; padding-top: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;기록 탭 - 월간 캘린더 히트맵 + 피드백 타임라인&lt;/p&gt;
&lt;/div&gt;
&lt;!-- 기능 4: AI 상담 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;4. Fortie에게 물어보기 (AI 상담)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7턴 멀티턴 대화&lt;/b&gt; &amp;mdash; 폼 기반 질문(text/select/multiselect) &amp;rarr; AI 응답. GPT-4o-mini 파인튜닝 모델 사용&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개인화&lt;/b&gt; &amp;mdash; 사용자의 상담/피드백/운세흐름 메타데이터 자동 수집 후 프롬프트에 주입&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쿼터 시스템&lt;/b&gt; &amp;mdash; 주간 7회 + 보너스 크레딧. 금지 키워드 정규식 필터 (관리자 설정)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 5: 콘텐츠 허브 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 모바일 이미지 3: 콘텐츠 --&gt;
&lt;div style=&quot;text-align: center; padding-bottom: 20px;&quot;&gt;&lt;img style=&quot;max-width: 320px; border-radius: 16px; border: 1px solid #e9ecef;&quot; src=&quot;https://cdn.myfortie.com/blog/fortie/37945d7d-f3d9-4c3c-bf71-07cd711c9613.png&quot; alt=&quot;Fortie 콘텐츠 - Fortune Flow&quot; /&gt;
&lt;p style=&quot;font-size: 12px; color: #868e96; padding-top: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;콘텐츠 탭 - Fortune Flow (재물/연애 3개월 흐름 분석)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;5. 콘텐츠 허브&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fortune Flow&lt;/b&gt; &amp;mdash; 재물/연애/커리어/건강 4카테고리, 3개월 90일 전통 사주 3단계 판정(good/neutral/caution) + LLM 분석&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판타지 전생 직업&lt;/b&gt; &amp;mdash; 사주 입력 &amp;rarr; 20개 직업 타입 + 오행 5스탯 + 희귀도 뱃지. 바이럴 공유 기능&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;썸 판별기 v4.0&lt;/b&gt; &amp;mdash; 일간 100조합 &amp;rarr; 6등급 판정 + 풀 사주 교차 분석 + 11점 양극 게이지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내일 예측&lt;/b&gt; &amp;mdash; 오늘 피드백 기록 시 내일 예측 해금. LLM 기반 + 규칙 폴백&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 6: 다국어 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;6. 글로벌 14개 언어 지원&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;14개 언어&lt;/b&gt; &amp;mdash; 한국어, 영어, 일본어, 프랑스어, 스페인어, 포르투갈어, 태국어, 베트남어, 인도네시아어, 힌디어, 독일어, 중국어(번체), 터키어, 아랍어&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언어별 AI 모델&lt;/b&gt; &amp;mdash; 주요 언어별 GPT-4o-mini 파인튜닝 모델 개별 운용&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;텍스트 DB&lt;/b&gt; &amp;mdash; 모든 UI 텍스트를 DB로 관리, I18nModule 인메모리 캐시, Accept-Language 헤더 기반 자동 전환&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 9: AI 파인튜닝 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;AI FINE-TUNING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 파인튜닝 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie의 AI는 단순한 프롬프트 호출이 아닙니다. &lt;b&gt;Claude Sonnet으로 정답지를 생성하고, GPT-4o-mini에 파인튜닝&lt;/b&gt;하는 구조입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;V6 파인튜닝 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1&lt;/b&gt; &amp;mdash; 사주/점성술 엔진으로 다양한 생년월일의 데이터 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 2&lt;/b&gt; &amp;mdash; Claude Sonnet에 엔진 데이터를 넣고 고품질 해석(정답지) 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3&lt;/b&gt; &amp;mdash; 정답지를 JSONL 형식으로 변환하여 GPT-4o-mini 파인튜닝&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 4&lt;/b&gt; &amp;mdash; 품질 검증 후 프로덕션 모델 교체&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;파인튜닝 모델 종류&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;운세 해석&lt;/b&gt; &amp;mdash; 주요 언어별 개별 파인튜닝 모델&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상담&lt;/b&gt; &amp;mdash; 멀티턴 상담 전용 파인튜닝 모델&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예측&lt;/b&gt; &amp;mdash; 내일 예측 전용 모델. 엔진 데이터 + 사용자 메타데이터 기반&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 Claude가 아닌 GPT-4o-mini로 파인튜닝?&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;Claude Sonnet의 해석 품질은 훨씬 높지만, &lt;b&gt;API 비용과 응답 속도&lt;/b&gt; 때문에 프로덕션에서 매 요청마다 Claude를 호출하기 어렵습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;해결책: Claude가 만든 &lt;b&gt;고품질 정답지&lt;/b&gt;를 GPT-4o-mini에 학습시켜, Claude 수준의 품질을 GPT-4o-mini 가격으로 서빙합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 대시보드의 &lt;b&gt;프롬프트 테스트&lt;/b&gt; 페이지에서 GPT-4o-mini 파인튜닝 vs Claude 정답지를 나란히 비교할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 10: TROUBLESHOOTING --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1: 결정론적 엔진 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;2개 엔진의 결정론적 일관성 보장&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;사주 엔진과 점성술 엔진은 완전히 다른 체계입니다. 하나는 60갑자 기반의 동양 역학이고, 다른 하나는 Swiss Ephemeris 기반의 서양 점성술입니다. 이 &lt;b&gt;두 결과를 하나의 일관된 해석으로 통합&lt;/b&gt;해야 했습니다. 동시에, 같은 입력이면 항상 같은 결과를 반환하는 결정론적 특성을 유지해야 합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - 엔진 분리 + 해석 레이어 통합&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;각 엔진은 순수 수학 계산만 수행하고, AI 해석 레이어에서 두 결과를 통합합니다. 엔진 결과는 캐싱하여 동일 입력에 항상 동일한 결과를 보장합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// fortune.service.ts
async getTodayFortune(userId: number) {
  // 1. 사주 엔진 (결정론적 계산)
  const sajuResult = this.sajuEngine.calculate(birthDate, birthTime, gender);
  // 4주, 십성, 충/합, 신살, 용신 등

  // 2. 점성술 엔진 (Swiss Ephemeris)
  const stellarResult = this.stellarEngine.calculate(birthDate, birthTime, location);
  // 행성 위치, 트랜짓, 하우스, 어스펙트

  // 3. AI 해석 (파인튜닝 모델)
  const interpretation = await this.interpretationService.interpret({
    saju: sajuResult,
    stellar: stellarResult,
    userProfile: await this.getUserProfile(userId),
  });

  // 4. 군중 통계 병합
  const crowdStats = await this.getCrowdStats(sajuResult.dayPillar);

  return { saju: sajuResult, stellar: stellarResult, interpretation, crowd: crowdStats };
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2: 파인튜닝 품질 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT-4o-mini 파인튜닝 시 Temperature보다 중요한 것&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;파인튜닝 후에도 GPT-4o-mini가 &lt;b&gt;비슷한 표현을 반복&lt;/b&gt;하거나, 특정 패턴에 고착되는 문제가 발생했습니다. Temperature를 올려도 다양성이 크게 개선되지 않았습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - Frequency/Presence Penalty 튜닝&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Temperature보다 &lt;b&gt;Frequency Penalty와 Presence Penalty&lt;/b&gt;가 훨씬 효과적이었습니다. 같은 토큰의 반복을 억제하고, 새로운 토픽 도입을 장려하는 방식으로 다양성을 확보했습니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// interpretation.service.ts - OpenAI 호출 설정
const response = await this.openai.chat.completions.create({
  model: this.getModelId(language),  // 언어별 파인튜닝 모델
  messages: [
    { role: 'system', content: systemPrompt },
    { role: 'user', content: JSON.stringify(engineData) },
  ],
  temperature: 0.7,
  frequency_penalty: 0.3,   // 같은 토큰 반복 억제
  presence_penalty: 0.2,    // 새로운 토픽 도입 장려
  max_tokens: 2000,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3: WebView auth 주입 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter WebView에서 Zustand 인증 상태 주입 타이밍&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter 네이티브에서 로그인 후, WebView의 Zustand 스토어에 인증 정보를 넘겨야 합니다. 하지만 &lt;b&gt;WebView가 로드되기 전에 localStorage에 데이터를 써야&lt;/b&gt; 하고, 기존 사용자의 &lt;b&gt;언어 설정은 덮어쓰면 안 됩니다&lt;/b&gt;.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - AT_DOCUMENT_START 주입 + 선택적 머지&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// WebViewPage.dart
initialUserScripts: UnmodifiableListView([
  UserScript(
    source: '''
      (function() {
        // 기존 언어 설정 보존
        var existing = localStorage.getItem('language-storage');
        var savedLang = null;
        if (existing) {
          try { savedLang = JSON.parse(existing).state.language; } catch(e) {}
        }

        // auth 상태 주입 (Zustand persist 포맷)
        localStorage.setItem('auth-storage', '$authJson');

        // 언어는 기존값 우선, 없으면 네이티브 값
        if (savedLang) {
          localStorage.setItem('language-storage',
            JSON.stringify({state:{language: savedLang}, version: 0}));
        }
      })();
    ''',
    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
  ),
]),&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 4: 개인 정확도 통계 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 04&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;개인 정확도 패턴 감지의 통계적 신뢰성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;충이 있는 날 변화를 경험한 비율 43%&quot;라는 패턴을 보여줄 때, 데이터가 5건이면 의미가 없습니다. &lt;b&gt;통계적으로 유의미한 패턴만 노출&lt;/b&gt;해야 했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - Chi-square + Lift 기반 분석 모듈&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 분석 모듈에서 &lt;b&gt;카이제곱 검정(Chi-square)&lt;/b&gt;으로 통계적 유의성을 확인하고, &lt;b&gt;Lift&lt;/b&gt; 값으로 패턴의 강도를 측정합니다. 마일스톤별로 최소 데이터 수 임계값을 두어, 충분한 데이터가 쌓이기 전에는 패턴을 노출하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// analysis.service.ts
analyzePattern(feedbacks: Feedback[], engineData: EngineResult) {
  // 카이제곱 검정: 관찰값 vs 기대값
  const observed = countByCondition(feedbacks, condition);
  const expected = feedbacks.length * baseRate;
  const chiSquare = Math.pow(observed - expected, 2) / expected;

  // p-value &amp;lt; 0.05인 경우만 패턴으로 인정
  if (chiSquare &amp;lt; 3.841) return null;

  // Lift: 조건부 발생률 / 전체 발생률
  const lift = (observed / conditionCount) / baseRate;
  return { pattern, confidence: chiSquare, strength: lift };
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 5: 텍스트 DB 다국어 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 05&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;다국어 텍스트의 성능과 관리 편의성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;14개 언어 x 수천 개 텍스트를 매번 DB에서 조회하면 성능 병목이 됩니다. 하지만 관리자가 수시로 텍스트를 변경할 수 있어야 합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - I18nModule 인메모리 캐시 + 관리자 수동 리프레시&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;서버 시작 시 전체 텍스트를 인메모리에 로드하고, 관리자 대시보드에서 &lt;b&gt;캐시 리프레시&lt;/b&gt; 버튼을 누르면 즉시 갱신됩니다. DB 쿼리 0회로 텍스트를 서빙합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// i18n.module.ts
@Global()
@Module({})
export class I18nModule implements OnModuleInit {
  private cache = new Map&amp;lt;string, Map&amp;lt;string, string&amp;gt;&amp;gt;();

  async onModuleInit() {
    await this.loadAll();  // 서버 시작 시 전체 로드
  }

  getText(domain: string, key: string, lang: string): string {
    return this.cache.get(`${domain}:${key}`)?.get(lang) ?? key;
  }

  async refresh() {  // 관리자 호출: POST /admin/texts/cache-refresh
    this.cache.clear();
    await this.loadAll();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 6: 커스텀 SVG 차트 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 06&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;커스텀 차트를 라이브러리 없이 구현&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;ArcGauge, DonutChart, NatalChart(천궁도), WuxingBars(오행), EnergyMeter, CompassDiagram 등 &lt;b&gt;다양한 데이터 시각화 차트&lt;/b&gt;가 필요했습니다. Chart.js나 D3를 쓰면 디자인 일관성이 깨지고 번들 크기가 커집니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - 순수 SVG + React 컴포넌트&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;모든 차트를 &lt;b&gt;SVG path와 circle&lt;/b&gt;로 직접 구현했습니다. 글래스모피즘 디자인 토큰(컬러, 그라디언트, 반경)을 그대로 적용할 수 있고, 차트 라이브러리 의존성 0으로 번들 크기를 최소화했습니다. Framer Motion과 결합하여 차트 진입 애니메이션도 자연스럽게 처리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 12: TECH STACK --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TECH STACK&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택 총정리&lt;/p&gt;
&lt;!-- 백엔드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 (fortune-api)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Runtime&lt;/b&gt; &amp;mdash; NestJS 11.0, TypeScript, Node.js&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Database&lt;/b&gt; &amp;mdash; PostgreSQL 16, TypeORM (마이그레이션)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Auth&lt;/b&gt; &amp;mdash; JWT, Passport (Google/Apple OAuth)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI&lt;/b&gt; &amp;mdash; OpenAI GPT-4o-mini (파인튜닝), Claude Sonnet (정답지)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Engine&lt;/b&gt; &amp;mdash; 사주(자체 구현), Swiss Ephemeris 0.5.17 (점성술)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Push&lt;/b&gt; &amp;mdash; Firebase Admin SDK (FCM)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Storage&lt;/b&gt; &amp;mdash; Cloudflare R2 (S3 호환 CDN)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;i18n&lt;/b&gt; &amp;mdash; 14개 언어, I18nModule 인메모리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Test&lt;/b&gt; &amp;mdash; Jest (유닛 + E2E)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 웹 프론트 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;웹 프론트엔드 (fortune-web)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Framework&lt;/b&gt; &amp;mdash; Next.js 14 (App Router), TypeScript&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;State&lt;/b&gt; &amp;mdash; Zustand (클라이언트), TanStack Query v5 (서버)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Style&lt;/b&gt; &amp;mdash; Tailwind CSS 3.4 + 커스텀 디자인 토큰&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Animation&lt;/b&gt; &amp;mdash; Framer Motion 12.34, Lottie React 2.4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Icons&lt;/b&gt; &amp;mdash; 커스텀 글래스모피즘 SVG&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Charts&lt;/b&gt; &amp;mdash; 커스텀 SVG (라이브러리 미사용)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Font&lt;/b&gt; &amp;mdash; Pretendard Variable (CDN)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 모바일 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;모바일 (fortune-app)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Framework&lt;/b&gt; &amp;mdash; Flutter 3, Dart 3.10.4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WebView&lt;/b&gt; &amp;mdash; flutter_inappwebview 6.1.5&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Auth&lt;/b&gt; &amp;mdash; google_sign_in 6.2.2, sign_in_with_apple 6.1.4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Push&lt;/b&gt; &amp;mdash; firebase_messaging 15.2.5, flutter_local_notifications 19.2.1&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Config&lt;/b&gt; &amp;mdash; firebase_remote_config 5.3.0, firebase_analytics 11.5.2&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 관리자 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;관리자 대시보드 (admin-web)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Framework&lt;/b&gt; &amp;mdash; React 19 + Vite&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Routing&lt;/b&gt; &amp;mdash; React Router v7&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;State&lt;/b&gt; &amp;mdash; Zustand + TanStack Query&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Charts&lt;/b&gt; &amp;mdash; Recharts&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Style&lt;/b&gt; &amp;mdash; TailwindCSS 4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Editor&lt;/b&gt; &amp;mdash; TipTap (Markdown/HTML)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 인프라 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;인프라 / 배포&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Backend&lt;/b&gt; &amp;mdash; Docker 멀티스테이지 빌드 + Docker Compose&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Frontend&lt;/b&gt; &amp;mdash; S3 + CloudFront (CDN 배포)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Storage&lt;/b&gt; &amp;mdash; Cloudflare R2 (cdn.myfortie.com)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain&lt;/b&gt; &amp;mdash; myfortie.com (Web), api.myfortie.com (API)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ================================================================== --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 섹션 14: CONCLUSION --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ================================================================== --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie는 &lt;b&gt;&quot;운세를 보는 앱&quot;이 아니라 &quot;기록하는 앱&quot;&lt;/b&gt;입니다. 사주/점성술 엔진의 결정론적 계산과, GPT-4o-mini 파인튜닝 모델의 자연어 해석, 사용자 피드백의 크라우드 통계를 결합하여 기록할수록 정확해지는 경험을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로 가장 도전적이었던 부분은 &lt;b&gt;2개 이질적 엔진의 통합&lt;/b&gt;과 &lt;b&gt;AI 파인튜닝 파이프라인 구축&lt;/b&gt;이었습니다. Claude Sonnet으로 정답지를 만들고 GPT-4o-mini에 학습시키는 구조는, 품질과 비용 사이에서 실용적인 균형점을 찾은 결과입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;또한, Flutter 하이브리드 WebView 구조는 &lt;b&gt;네이티브의 강점(인증/알림/강제업데이트)&lt;/b&gt;과 &lt;b&gt;웹의 유연성(빠른 콘텐츠 업데이트)&lt;/b&gt;을 모두 취할 수 있는 전략이었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;3개 레포지토리(백엔드 + 웹 + 모바일)와 관리자 대시보드까지 포함하면 풀스택의 모든 레이어를 한 사람이 설계하고 구현한 프로젝트입니다. 부족한 부분도 많지만, &lt;b&gt;사용자의 기록이 쌓여 서비스 자체가 진화하는 구조&lt;/b&gt;를 만들었다는 점에서 의미가 있었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고 자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://myfortie.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Fortie Web&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kfortune&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Google Play&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/app/id6758990823&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;App Store&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://platform.openai.com/docs/guides/fine-tuning&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OpenAI Fine-tuning Guide&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://www.astro.com/swisseph/swephprg.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swiss Ephemeris&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://nestjs.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NestJS&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://nextjs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Next.js&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 스토어 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie (포티) 다운로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #667eea; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://myfortie.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Web&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kfortune&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/app/id6758990823&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Fortie는 사주/점성술이라는 전통적인 영역에 AI 파인튜닝과 크라우드소싱 통계를 결합한 실험적 프로젝트입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드(NestJS), 웹 프론트(Next.js), 모바일(Flutter), 관리자(React) 4개 프로젝트를 풀스택으로 개발하면서 얻은 경험을 공유했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;서비스에 대한 의견이나 피드백은 언제든 환영합니다!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발</category>
      <category>flutter</category>
      <category>fortie</category>
      <category>nestJS</category>
      <category>next.js</category>
      <category>PostgreSQL</category>
      <category>TypeScript</category>
      <category>사주</category>
      <category>점성술</category>
      <category>포티</category>
      <category>풀스택</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/653</guid>
      <comments>https://kwonputer.tistory.com/653#entry653comment</comments>
      <pubDate>Tue, 17 Mar 2026 14:36:15 +0900</pubDate>
    </item>
    <item>
      <title>[AI 블로그 실험] D+31 리포트: 10개국 블로그, 한 달간의 실제 데이터 공개</title>
      <link>https://kwonputer.tistory.com/651</link>
      <description>&lt;div style=&quot;text-align:center;padding:40px 20px 10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:14px;color:#6b7280;padding:0;&quot;&gt;D+31 &amp;middot; 2026년 03월 04일 기준&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;height:10px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;padding:0 20px;&quot;&gt;
  &lt;p style=&quot;font-size:16px;color:#374151;line-height:1.8;padding:0;&quot;&gt;
    저는 지금 하나의 실험을 진행하고 있습니다.
  &lt;/p&gt;
  &lt;div style=&quot;height:8px;&quot;&gt;&lt;/div&gt;
  &lt;p style=&quot;font-size:16px;color:#374151;line-height:1.8;padding:0;&quot;&gt;
    &lt;b&gt;&quot;AI가 작성한 콘텐츠만으로 10개국 블로그를 1년간 운영하면, 어디까지 성장할 수 있을까?&quot;&lt;/b&gt;
  &lt;/p&gt;
  &lt;div style=&quot;height:8px;&quot;&gt;&lt;/div&gt;
  &lt;p style=&quot;font-size:16px;color:#374151;line-height:1.8;padding:0;&quot;&gt;
    한국어, 영어, 독일어, 일본어, 프랑스어, 스페인어, 포르투갈어, 네덜란드어, 이탈리아어, 터키어.
    총 &lt;b&gt;10개 언어&lt;/b&gt;로, 각각 독립된 블로그를 운영하고 있습니다.
    모든 글은 AI가 작성하고, 자동으로 발행됩니다.
    사람이 하는 건 시스템을 만들고 지켜보는 것뿐입니다.
  &lt;/p&gt;
  &lt;div style=&quot;height:8px;&quot;&gt;&lt;/div&gt;
  &lt;p style=&quot;font-size:16px;color:#374151;line-height:1.8;padding:0;&quot;&gt;
    이 글은 &lt;b&gt;최근 30일간의 실제 데이터&lt;/b&gt;를 바탕으로 한 중간 리포트입니다.
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;height:30px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;padding:0 20px;&quot;&gt;
  &lt;h2 style=&quot;font-size:22px;color:#1e293b;padding:0 0 5px 0;&quot;&gt;10개 블로그, 지금 이 정도입니다&lt;/h2&gt;
  &lt;div style=&quot;height:5px;&quot;&gt;&lt;/div&gt;
  &lt;table style=&quot;width:100%;border-collapse:collapse;border:1px solid #e2e8f0;&quot;&gt;
    &lt;thead&gt;
      &lt;tr style=&quot;background-color:#475569;&quot;&gt;
        &lt;th style=&quot;padding:12px;text-align:left;color:#ffffff;font-size:14px;&quot;&gt;블로그&lt;/th&gt;
        &lt;th style=&quot;padding:12px;text-align:left;color:#ffffff;font-size:14px;&quot;&gt;언어&lt;/th&gt;
        &lt;th style=&quot;padding:12px;text-align:right;color:#ffffff;font-size:14px;&quot;&gt;방문자&lt;/th&gt;
        &lt;th style=&quot;padding:12px;text-align:right;color:#ffffff;font-size:14px;&quot;&gt;페이지뷰&lt;/th&gt;
        &lt;th style=&quot;padding:12px;text-align:right;color:#ffffff;font-size:14px;&quot;&gt;검색 노출&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
    &lt;tr style=&quot;background-color:#f8f9fa;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwonputer.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwonputer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;한국어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;39&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;203&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#ffffff;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwonglish.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwonglish&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;영어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;56&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;105&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;103&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#f8f9fa;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwonnen.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwonnen&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;독일어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;58&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;89&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;96&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#ffffff;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwonteki.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwonteki&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;일본어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;48&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;62&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#f8f9fa;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwontenu.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwontenu&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;프랑스어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;57&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;70&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#ffffff;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwonsejo.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwonsejo&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;스페인어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;111&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;140&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;29&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#f8f9fa;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwontudo.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwontudo&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;포르투갈어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;49&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;63&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#ffffff;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwonnis.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwonnis&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;네덜란드어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;51&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;54&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#f8f9fa;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwontento.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwontento&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;이탈리아어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;69&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;74&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#ffffff;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-weight:bold;&quot;&gt;
        &lt;a href=&quot;https://kwontrol.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;Kwontrol&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;color:#6b7280;&quot;&gt;터키어&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;54&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;57&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;border-bottom:1px solid #e2e8f0;text-align:right;&quot;&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background-color:#1e293b;&quot;&gt;
      &lt;td style=&quot;padding:10px 12px;color:#ffffff;font-weight:bold;&quot; colspan=&quot;2&quot;&gt;합계&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;color:#ffffff;text-align:right;font-weight:bold;&quot;&gt;592&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;color:#ffffff;text-align:right;font-weight:bold;&quot;&gt;917&lt;/td&gt;
      &lt;td style=&quot;padding:10px 12px;color:#ffffff;text-align:right;font-weight:bold;&quot;&gt;327&lt;/td&gt;
    &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
  &lt;div style=&quot;height:8px;&quot;&gt;&lt;/div&gt;
  &lt;p style=&quot;font-size:13px;color:#9ca3af;padding:0;&quot;&gt;* 방문자·페이지뷰: Google Analytics / 검색 노출: Google Search Console&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;height:30px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;padding:0 20px;&quot;&gt;&lt;h2 style=&quot;font-size:22px;color:#1e293b;padding:0;&quot;&gt;각 블로그를 좀 더 자세히 보면&lt;/h2&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    1. Kwonputer
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 한국어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwonputer.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwonputer.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;39&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;203&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;37.1%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;4&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. 권퓨터: Kwonputer - 오늘도 뭔가 만들거나 놀거나 (112뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. [신수강호전] 제1화: 복수의 서막 - 권퓨터: Kwonputer (6뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. Page Not Found - 권퓨터: Kwonputer (5뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    2. Kwonglish
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 영어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwonglish.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwonglish.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;56&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;105&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;20.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;103&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
  &lt;p style=&quot;padding:0;line-height:2.2;&quot;&gt;&lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;dell xps vs macbook pro 2025 2026 comparison which is better&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;dell xps vs macbook pro comparison 2026&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;hidden gem nyc restaurants&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;hidden gem restaurants nyc&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;macbook pro vs dell xps 2025 2026 comparison&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwonglish - (95뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. 7 Best Hidden Gem Restaurants in New York ... (4뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. [Productivity] The Ultimate Developer Work... (4뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    3. Kwonnen
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 독일어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwonnen.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwonnen.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;58&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;89&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;17.1%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;96&lt;/b&gt;회 &amp;middot; 검색 클릭 &lt;b&gt;1&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
  &lt;p style=&quot;padding:0;line-height:2.2;&quot;&gt;&lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;progressive web app 2026&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;&quot;postgrest&quot;&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;andt design&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;ant design vs material ui&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;chakra ui&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwonnen - (82뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. Die 7 besten Geheimtipp-Restaurants in Ber... (2뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. Gesundheit &amp; Wellness - Kwonnen (2뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    4. Kwonteki
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 일본어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwonteki.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwonteki.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;48&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;62&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;10.3%&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwonteki - (58뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. 東京で本当に美味しい隠れ家レストラン7選 — 地元民が通う名店ガイド - Kwonteki (3뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. Page Not Found - Kwonteki (1뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    5. Kwontenu
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 프랑스어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwontenu.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwontenu.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;57&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;70&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;15.4%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;60&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;사람들이 이런 키워드로 찾고 있습니다:&lt;/p&gt;
  &lt;p style=&quot;padding:0;line-height:2.2;&quot;&gt;&lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;(inurl:comment) collisions&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;framework laptop&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;framework+laptop&lt;/span&gt;, &lt;span style=&quot;background-color:#e0e7ff;padding:2px 8px;font-size:13px;color:#3730a3;&quot;&gt;pc portable développeur&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwontenu - (67뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. [Flutter] Comment optimiser les performanc... (2뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. Page Not Found - Kwontenu (1뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    6. Kwonsejo
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 스페인어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwonsejo.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwonsejo.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;111&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;140&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;7.6%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;29&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwonsejo - (123뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. [IA &amp; ML] Cómo crear tu primer chatbot con... (3뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. sistema login JWT completo - Kwonsejo (3뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    7. Kwontudo
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 포르투갈어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwontudo.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwontudo.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;49&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;63&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;9.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;24&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwontudo - (59뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. DevOps &amp; Cloud - Kwontudo (1뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. Frontend - Kwontudo (1뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    8. Kwonnis
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 네덜란드어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwonnis.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwonnis.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;51&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;54&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;3.8%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;4&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwonnis - (53뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. Page Not Found - Kwonnis (1뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    9. Kwontento
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 이탈리아어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwontento.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwontento.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;69&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;74&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;11.3%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;4&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwontento - (66뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. Sviluppo - Kwontento (3뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. mercato del lavoro tech - Kwontento (2뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:1px;background-color:#e2e8f0;&quot;&gt;&lt;/div&gt;
&lt;div style=&quot;height:15px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;background-color:#f1f5f9;padding:15px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:18px;font-weight:bold;color:#1e293b;padding:0;&quot;&gt;
    10. Kwontrol
    &lt;span style=&quot;font-size:14px;font-weight:normal;color:#64748b;&quot;&gt;— 터키어&lt;/span&gt;
  &lt;/p&gt;
  &lt;p style=&quot;font-size:14px;padding:5px 0 0 0;&quot;&gt;
    &lt;a href=&quot;https://kwontrol.com&quot; style=&quot;color:#2563eb;text-decoration:none;&quot;&gt;https://kwontrol.com&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:10px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:15px;color:#374151;line-height:1.8;padding:0;&quot;&gt;방문자 &lt;b&gt;54&lt;/b&gt;명 &amp;middot; 페이지뷰 &lt;b&gt;57&lt;/b&gt; &amp;middot; 참여율 &lt;b&gt;7.1%&lt;/b&gt; &amp;middot; 검색 노출 &lt;b&gt;3&lt;/b&gt;회&lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;padding:5px 20px;&quot;&gt;
  &lt;p style=&quot;font-size:13px;color:#6b7280;padding:0 0 5px 0;&quot;&gt;가장 많이 읽힌 글:&lt;/p&gt;
  
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;1. Kwontrol - (54뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;2. Page Not Found - Kwontrol (2뷰)&lt;/p&gt;
  &lt;p style=&quot;font-size:14px;color:#374151;padding:3px 0;&quot;&gt;3. [Verimlilik] Notion AI vs ChatGPT vs Claud... (1뷰)&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;height:30px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;padding:0 20px;&quot;&gt;
  &lt;h2 style=&quot;font-size:22px;color:#1e293b;padding:0 0 5px 0;&quot;&gt;앞으로의 계획&lt;/h2&gt;
  &lt;div style=&quot;height:5px;&quot;&gt;&lt;/div&gt;
  &lt;p style=&quot;font-size:16px;color:#374151;line-height:1.8;padding:0;&quot;&gt;
    실험은 계속됩니다. 지금은 D+31이고, 목표는 D+365입니다.
  &lt;/p&gt;
  &lt;div style=&quot;height:8px;&quot;&gt;&lt;/div&gt;
  &lt;p style=&quot;font-size:16px;color:#374151;line-height:1.8;padding:0;&quot;&gt;
    앞으로도 정기적으로 이런 중간 리포트를 공유할 예정입니다.
    궁금한 블로그가 있다면 직접 방문해 보세요.
    AI가 쓴 글이라는 걸 알아챌 수 있는지, 한번 확인해 보시는 것도 재미있을 겁니다.
  &lt;/p&gt;
&lt;/div&gt;

&lt;div style=&quot;height:30px;&quot;&gt;&lt;/div&gt;

&lt;div style=&quot;padding:15px 20px;background-color:#f8f9fa;text-align:center;&quot;&gt;
  &lt;p style=&quot;font-size:12px;color:#9ca3af;padding:0;&quot;&gt;2026년 03월 04일 기준 데이터 &amp;middot; Google Analytics + Search Console&lt;/p&gt;
&lt;/div&gt;</description>
      <category>IT &amp;middot; 테크/기술 트렌드</category>
      <category>AI blog experiment</category>
      <category>AI 글쓰기</category>
      <category>AI 블로그</category>
      <category>ai 콘텐츠</category>
      <category>Google Analytics</category>
      <category>검색 노출</category>
      <category>다국어 블로그</category>
      <category>블로그 성장기</category>
      <category>블로그 실험</category>
      <category>블로그 운영</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/651</guid>
      <comments>https://kwonputer.tistory.com/651#entry651comment</comments>
      <pubDate>Wed, 4 Mar 2026 10:14:52 +0900</pubDate>
    </item>
    <item>
      <title>서브 블로그 =&amp;gt; https://kwonputer.com/</title>
      <link>https://kwonputer.tistory.com/notice/649</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.com/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://kwonputer.com/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/notice/649</guid>
      <pubDate>Fri, 20 Feb 2026 15:06:42 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 오프라인 AI 자막 생성기 'K-Sub' - 인터넷 없이 로컬에서 Whisper 음성인식 + 번역 + LLM 후처리</title>
      <link>https://kwonputer.tistory.com/593</link>
      <description>&lt;!--
===============================================
포스트 메타 정보
===============================================
제목: [Python] 오프라인 AI 자막 생성기 'K-Sub' - 인터넷 없이 로컬에서 Whisper 음성인식 + 번역 + LLM 후처리

설명(검색용): 인터넷 연결 없이 100% 로컬에서 동작하는 AI 자막 생성기입니다. Whisper 음성 인식, KE-T5/NLLB 번역, Qwen LLM 후처리를 모두 오프라인으로 실행하여 영상 데이터가 외부로 유출되지 않습니다. 민감한 영상도 안심하고 자막 작업이 가능합니다.

태그 (10개):
1. Python
2. 오프라인 AI
3. 로컬 LLM
4. Whisper
5. 자막 생성기
6. 프라이버시
7. KE-T5
8. NLLB
9. Qwen
10. PyInstaller
===============================================
--&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 핵심 특징 배너 --&gt;
&lt;div style=&quot;background-color: #212529; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;CORE FEATURES&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #667eea; color: #fff; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; display: inline-block; margin: 4px;&quot;&gt;AI 5개 모델&lt;/span&gt; &lt;span style=&quot;background-color: #20c997; color: #fff; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; display: inline-block; margin: 4px;&quot;&gt;100% 오프라인&lt;/span&gt; &lt;span style=&quot;background-color: #fd7e14; color: #fff; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; display: inline-block; margin: 4px;&quot;&gt;완전한 프라이버시&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;K-Sub 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/kzpuP/dJMcadU9qXC/3sNItDKSgFuQUYopKk3QX1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/9j2tL/dJMcadU9qXD/uF6RtUMzQqDY05s2kdYRB1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cX1Jlr/dJMcaaxpLd1/YjhpzN5T41WMeFslLNx1RK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/KOLHQ/dJMcaaxpLd2/3kMCoFjKSkh4740woiA0U0/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kzpuP/dJMcadU9qXC/3sNItDKSgFuQUYopKk3QX1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/kzpuP/dJMcadU9qXC/3sNItDKSgFuQUYopKk3QX1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kzpuP/dJMcadU9qXC/3sNItDKSgFuQUYopKk3QX1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkzpuP%2FdJMcadU9qXC%2F3sNItDKSgFuQUYopKk3QX1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9j2tL/dJMcadU9qXD/uF6RtUMzQqDY05s2kdYRB1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/9j2tL/dJMcadU9qXD/uF6RtUMzQqDY05s2kdYRB1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9j2tL/dJMcadU9qXD/uF6RtUMzQqDY05s2kdYRB1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9j2tL%2FdJMcadU9qXD%2FuF6RtUMzQqDY05s2kdYRB1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX1Jlr/dJMcaaxpLd1/YjhpzN5T41WMeFslLNx1RK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cX1Jlr/dJMcaaxpLd1/YjhpzN5T41WMeFslLNx1RK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX1Jlr/dJMcaaxpLd1/YjhpzN5T41WMeFslLNx1RK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX1Jlr%2FdJMcaaxpLd1%2FYjhpzN5T41WMeFslLNx1RK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KOLHQ/dJMcaaxpLd2/3kMCoFjKSkh4740woiA0U0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/KOLHQ/dJMcaaxpLd2/3kMCoFjKSkh4740woiA0U0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KOLHQ/dJMcaaxpLd2/3kMCoFjKSkh4740woiA0U0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKOLHQ%2FdJMcaaxpLd2%2F3kMCoFjKSkh4740woiA0U0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #667eea; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://drive.google.com/file/d/1h9j0Q1UxqKLJ8JH3eL1rN13Xi7nkk22Y/view?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Windows&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://drive.google.com/file/d/1pMZykUSObZAGjl8X6lAZvMpZW2bdAPgq/view?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mac&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;K-Sub (Kwon-Subtitle)&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터넷 연결 없이 100% 로컬&lt;/b&gt;에서 동작하는 AI 자막 생성기입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Whisper 음성 인식 &amp;rarr; KE-T5/NLLB 번역 &amp;rarr; Qwen LLM 후처리까지 &lt;b&gt;모든 AI가 내 컴퓨터에서 실행&lt;/b&gt;되어, 영상 데이터가 외부로 전송되지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;오프라인 AI 파이프라인 구축&lt;/b&gt;, &lt;b&gt;NLLB&amp;rarr;KE-T5 2단계 번역&lt;/b&gt;, &lt;b&gt;고유명사 보호 시스템&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 오프라인인가?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;아키텍처&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;사용 모델&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: WHY OFFLINE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY OFFLINE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 오프라인 AI인가?&lt;/p&gt;
&lt;!-- 프라이버시 강조 카드 --&gt;
&lt;div style=&quot;background-color: #fff0f0; border: 1px solid #ffc9c9; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #e03131; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;클라우드 서비스의 문제점&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프라이버시 우려&lt;/b&gt; &amp;mdash; 영상을 외부 서버에 업로드해야 하므로, 민감한 콘텐츠는 사용하기 어렵습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터넷 의존성&lt;/b&gt; &amp;mdash; 네트워크 상태에 따라 속도가 달라지고, 오프라인에서는 사용 불가합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비용 문제&lt;/b&gt; &amp;mdash; 대부분의 고품질 서비스는 유료이며, 영상 길이에 따라 비용이 증가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 오프라인 AI 장점 카드 --&gt;
&lt;div style=&quot;background-color: #d3f9d8; border: 1px solid #8ce99a; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #2b8a3e; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;K-Sub의 해결책: 100% 로컬 AI&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완전한 프라이버시&lt;/b&gt; &amp;mdash; 모든 처리가 내 컴퓨터에서 이루어지므로, 영상 데이터가 외부로 유출되지 않습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오프라인 동작&lt;/b&gt; &amp;mdash; 인터넷 연결 없이도 완벽하게 동작합니다. 비행기 안에서도 자막 작업이 가능합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무료 + 무제한&lt;/b&gt; &amp;mdash; 한 번 설치하면 영상 길이와 개수에 상관없이 무료로 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;일본어/영어 영상에 한국어 자막을 붙이고 싶을 때, 기존 도구들은 &lt;b&gt;고유명사 처리가 엉망&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 애니메이션이나 드라마의 캐릭터 이름, 특수 용어가 제대로 번역되지 않는 문제가 있었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;고유명사를 보존하면서도 자연스러운 번역&lt;/b&gt;을 생성하고, &lt;b&gt;프라이버시까지 보장&lt;/b&gt;하는 도구를 직접 만들기로 했습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: ARCHITECTURE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;5단계 오프라인 AI 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5개의 AI 모델&lt;/b&gt;이 모두 로컬에서 순차적으로 실행됩니다. 인터넷 연결 없이 음성인식부터 품질 평가까지 완전한 파이프라인을 구축했습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;영상 파일 (mp4/mkv/avi)
    │
    ▼
┌─────────────────────────────────────┐
│  Step 1. 음성 인식 (Whisper)         │  &amp;larr; 로컬 AI #1
│  - OpenAI Whisper large-v3          │
│  - 장르별 initial prompt            │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 2. 고유명사 추출 (Qwen 1.5B)   │  &amp;larr; 로컬 AI #2
│  - 인명/지명/조직/용어 JSON 추출     │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 3. 번역                        │
│  - 일본어&amp;rarr;영어: NLLB-200            │  &amp;larr; 로컬 AI #3
│  - 영어&amp;rarr;한국어: KE-T5               │  &amp;larr; 로컬 AI #4
│  - EntityProtector로 고유명사 보호   │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 4. LLM 후처리 (Qwen 3B)       │  &amp;larr; 로컬 AI #5
│  - 고유명사 한국어 변환              │
│  - 품질 점수 계산 (0~100)           │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 5. SRT 저장 + GUI 편집         │
│  - 원문/번역문 병렬 미리보기         │
│  - 인라인 편집 모드                  │
└─────────────────────────────────────┘

※ 모든 AI가 내 컴퓨터에서 실행됩니다.
   인터넷 연결 불필요, 데이터 외부 전송 없음.&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능&lt;/p&gt;
&lt;!-- Whisper 음성 인식 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;Whisper 음성 인식 (로컬)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenAI Whisper large-v3&lt;/b&gt; &amp;mdash; 1550M 파라미터의 최신 음성 인식 모델이 내 컴퓨터에서 직접 실행됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장르별 initial prompt&lt;/b&gt; &amp;mdash; 애니메이션/드라마/영화/다큐/뉴스/강의/AV 7가지 장르별 힌트로 인식률 향상&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실시간 진행률&lt;/b&gt; &amp;mdash; verbose 출력 파싱으로 현재 인식 중인 자막 내용까지 GUI에 표시&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 번역 엔진 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;번역 엔진 KE-T5 + NLLB (로컬)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;영어 &amp;rarr; 한국어&lt;/b&gt; &amp;mdash; KE-T5 직접 번역으로 자연스러운 한국어 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일본어 &amp;rarr; 한국어&lt;/b&gt; &amp;mdash; NLLB(일&amp;rarr;영) + KE-T5(영&amp;rarr;한) 2단계 파이프라인으로 품질 향상&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EntityProtector&lt;/b&gt; &amp;mdash; 번역 전 고유명사를 플레이스홀더로 치환하여 보호, 번역 후 복원&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- LLM 후처리 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;LLM 후처리 Qwen2.5 (로컬)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고유명사 추출&lt;/b&gt; &amp;mdash; Qwen2.5-1.5B로 캐릭터/장소/조직/용어를 JSON으로 추출&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국어 변환&lt;/b&gt; &amp;mdash; Qwen2.5-3B로 일본어/영어 고유명사를 한국어 발음/의미로 변환&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;품질 점수&lt;/b&gt; &amp;mdash; 6가지 기준으로 0~100점 계산, 70점 미만은 &quot;수정 필요&quot; 표시&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- GUI 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e03131; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;데스크탑 GUI (tkinter)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실시간 진행률&lt;/b&gt; &amp;mdash; 5단계별 프로그레스 바와 현재 처리 중인 자막 내용 표시&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자막 미리보기&lt;/b&gt; &amp;mdash; 원문/번역문 병렬 표시, 인라인 편집 지원&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고유명사 사전&lt;/b&gt; &amp;mdash; 작품별 사전 생성/관리/가져오기/내보내기 (JSON)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작업 이어하기&lt;/b&gt; &amp;mdash; 중간에 중단한 작업 상태 저장/복원 기능&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 디바이스 감지 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;디바이스 자동 감지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NVIDIA GPU&lt;/b&gt; &amp;mdash; nvidia-smi로 VRAM 감지, 12GB 이상이면 large-v3 + LLM 추천&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Apple Silicon&lt;/b&gt; &amp;mdash; sysctl로 MPS 감지, 통합 메모리 기반 최적 설정 추천&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CPU&lt;/b&gt; &amp;mdash; 메모리 부족 시 small 모델 + LLM OFF 자동 전환&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Whisper 취소 불가능 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;model.transcribe()&lt;/code&gt;가 블로킹 함수여서 중간 취소가 불가능했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;24분 영상 처리에 약 50분이 소요되는데, 잘못된 설정으로 시작하면 처음부터 다시 해야 하는 상황이 발생했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 8px; padding: 16px; margin-bottom: 16px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;시도 1 &amp;mdash; subprocess 분리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;별도 프로세스로 분리하려 했으나, PyInstaller 빌드 시 &lt;code style=&quot;background-color: #e9ecef; padding: 2px 6px; border-radius: 4px; font-size: 13px;&quot;&gt;sys.executable&lt;/code&gt;이 exe를 가리켜서 실패&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; ctypes로 스레드 강제 종료&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;별도 스레드에서 Whisper를 실행하고, ctypes의 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;PyThreadState_SetAsyncExc&lt;/code&gt;로 SystemExit를 주입하여 강제 종료&lt;/p&gt;
&lt;pre class=&quot;elm&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# ctypes로 스레드 강제 종료
thread_id = whisper_thread.ident
ctypes.pythonapi.PyThreadState_SetAsyncExc(
    ctypes.c_ulong(thread_id),
    ctypes.py_object(SystemExit)
)
# 취소 후 GPU 메모리 해제
torch.cuda.empty_cache()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;일본어&amp;rarr;한국어 직접 번역 품질&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;오프라인에서 사용 가능한 일&amp;rarr;한 직접 번역 모델의 품질이 낮고, 고유명사가 완전히 왜곡되었습니다. 문장 구조도 어색하고 의미가 통하지 않는 경우가 많았습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; NLLB&amp;rarr;KE-T5 2단계 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;영어를 중간 언어로 활용하여 품질을 크게 향상시켰습니다. 두 모델 모두 로컬에서 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# 일본어 &amp;rarr; 한국어 2단계 번역 (모두 로컬)
# Step 1: NLLB (일&amp;rarr;영) - 로컬 실행
english = nllb_translate(japanese, &quot;jpn_Jpan&quot;, &quot;eng_Latn&quot;)

# Step 2: KE-T5 (영&amp;rarr;한) - 로컬 실행
korean = ket5_translate(english, &quot;en&quot;, &quot;ko&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;번역 시 고유명사 파괴&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;ルフィ(루피)&quot;가 번역 후 &quot;루피&quot;가 아닌 엉뚱한 텍스트로 변환되는 문제가 있었습니다. 번역 모델이 고유명사를 일반 단어로 인식하여 왜곡시키는 현상이었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; EntityProtector 패턴&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;번역 전 고유명사를 플레이스홀더로 치환하고, 번역 후 복원하는 방식으로 해결했습니다.&lt;/p&gt;
&lt;pre class=&quot;vala&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# EntityProtector 동작 방식
# 보호: &quot;ルフィは悪魔の実を食べた&quot;
#    &amp;rarr; &quot;ENTITYNAME0はENTITYNAME1を食べた&quot;

# 번역: &quot;ENTITYNAME0이 ENTITYNAME1을 먹었다&quot;

# 복원: &quot;루피가 악마의 열매를 먹었다&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 4 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 04&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Windows 인코딩 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;ffprobe subprocess에서 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;UnicodeDecodeError: 'cp949'&lt;/code&gt; 오류가 발생했습니다. Windows의 기본 인코딩이 cp949이기 때문이었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; UTF-8 명시 + 콘솔 숨김&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# Windows subprocess 설정
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

result = subprocess.run(
    ffprobe_cmd,
    capture_output=True,
    encoding='utf-8',
    errors='ignore',
    startupinfo=startupinfo
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 5 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 05&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GUI 응답 없음 (Not Responding)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Whisper가 메인 스레드에서 실행되면 GUI가 완전히 멈춰서 &quot;응답 없음&quot; 상태가 되었습니다. 사용자가 취소 버튼을 누를 수도 없었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; threading + queue 패턴&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;작업 스레드에서 queue에 메시지를 push하고, 메인 스레드에서 50ms 간격으로 poll하여 GUI를 업데이트합니다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# threading + queue 패턴
message_queue = queue.Queue()

def worker_thread():
    # 작업 수행 후 결과를 queue에 push
    message_queue.put((&quot;progress&quot;, 50, &quot;처리 중...&quot;))

def poll_queue():
    while not message_queue.empty():
        msg_type, *data = message_queue.get()
        update_gui(msg_type, data)
    root.after(50, poll_queue)  # 50ms 후 다시 호출&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: MODELS --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;MODELS&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;로컬에서 실행되는 5개 AI 모델&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Whisper large-v3&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;음성 인식 | 1550M 파라미터 | OpenAI | 로컬 실행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;KE-T5-base&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;영&amp;rarr;한 번역 | ~220M 파라미터 | seongs/ke-t5-base-aihub | 로컬 실행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;NLLB-200-distilled&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;일&amp;rarr;영 번역 | 600M 파라미터 | Facebook | 로컬 실행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Qwen2.5-1.5B-Instruct&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;고유명사 추출 | 1.5B 파라미터 | Alibaba | 로컬 실행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Qwen2.5-3B-Instruct&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;LLM 후처리 | 3B 파라미터 | Alibaba | 로컬 실행&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://github.com/openai/whisper&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OpenAI Whisper&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://huggingface.co/seongs/ke-t5-base-aihub-koen-translation-integrated-10m-en-to-ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KE-T5 HuggingFace&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://huggingface.co/facebook/nllb-200-distilled-600M&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NLLB-200 HuggingFace&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://huggingface.co/Qwen/Qwen2.5-3B-Instruct&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Qwen2.5 HuggingFace&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pyinstaller.org/en/stable/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PyInstaller&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;100% 오프라인 동작&lt;/b&gt; &amp;mdash; 인터넷 연결 없이 5개의 AI 모델이 모두 내 컴퓨터에서 실행됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완전한 프라이버시&lt;/b&gt; &amp;mdash; 영상 데이터가 외부로 전송되지 않아, 민감한 콘텐츠도 안심하고 처리할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NLLB&amp;rarr;KE-T5 2단계 번역&lt;/b&gt; &amp;mdash; 일&amp;rarr;한 직접 번역보다 영어를 중간 언어로 거치는 것이 더 나은 결과를 내는 것을 발견했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EntityProtector + LLM 후처리&lt;/b&gt; &amp;mdash; 이중 안전장치로 고유명사 보존율을 크게 향상시켰습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디바이스 자동 감지&lt;/b&gt; &amp;mdash; 사용자 하드웨어에 맞춰 최적 설정을 자동 추천하여 진입 장벽을 최소화했습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 프로그램 다운로드 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;K-Sub 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/NqCva/dJMcaiCaS5a/eyI7WUxfDwYdhbOekhDzp0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bymfRz/dJMcaia9lTh/5Wj8P4xg0FGAJsgGcTZdOk/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/CMVo1/dJMcaaqDIsd/bcRI8ocJKt27N1P5lLG82k/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/rZN8e/dJMcaaqDIse/eHFYwOskUktOkVmT6F5Cxk/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NqCva/dJMcaiCaS5a/eyI7WUxfDwYdhbOekhDzp0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/NqCva/dJMcaiCaS5a/eyI7WUxfDwYdhbOekhDzp0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NqCva/dJMcaiCaS5a/eyI7WUxfDwYdhbOekhDzp0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNqCva%2FdJMcaiCaS5a%2FeyI7WUxfDwYdhbOekhDzp0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bymfRz/dJMcaia9lTh/5Wj8P4xg0FGAJsgGcTZdOk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bymfRz/dJMcaia9lTh/5Wj8P4xg0FGAJsgGcTZdOk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bymfRz/dJMcaia9lTh/5Wj8P4xg0FGAJsgGcTZdOk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbymfRz%2FdJMcaia9lTh%2F5Wj8P4xg0FGAJsgGcTZdOk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CMVo1/dJMcaaqDIsd/bcRI8ocJKt27N1P5lLG82k/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/CMVo1/dJMcaaqDIsd/bcRI8ocJKt27N1P5lLG82k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CMVo1/dJMcaaqDIsd/bcRI8ocJKt27N1P5lLG82k/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCMVo1%2FdJMcaaqDIsd%2FbcRI8ocJKt27N1P5lLG82k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rZN8e/dJMcaaqDIse/eHFYwOskUktOkVmT6F5Cxk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/rZN8e/dJMcaaqDIse/eHFYwOskUktOkVmT6F5Cxk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rZN8e/dJMcaaqDIse/eHFYwOskUktOkVmT6F5Cxk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrZN8e%2FdJMcaaqDIse%2FeHFYwOskUktOkVmT6F5Cxk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #667eea; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://drive.google.com/file/d/1h9j0Q1UxqKLJ8JH3eL1rN13Xi7nkk22Y/view?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Windows&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://drive.google.com/file/d/1pMZykUSObZAGjl8X6lAZvMpZW2bdAPgq/view?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mac&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 &quot;일본어 영상에 한국어 자막을 붙이고 싶다&quot;는 개인적인 필요에서 시작한 프로젝트였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 기존 클라우드 서비스들은 &lt;b&gt;프라이버시 문제&lt;/b&gt;가 있었고, 민감한 콘텐츠는 업로드하기 꺼려졌습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;완전히 오프라인에서 동작하는 AI 자막 생성기&lt;/b&gt;를 만들기로 결심했고, Whisper부터 LLM까지 5개의 AI 모델을 로컬에서 실행하는 파이프라인을 완성했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &quot;일&amp;rarr;한 직접 번역보다 &lt;b&gt;영어를 중간 언어로 거치는 2단계 번역&lt;/b&gt;이 더 자연스럽다&quot;는 발견은 이 프로젝트의 핵심 인사이트였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;이 도구가 저처럼 &lt;b&gt;프라이버시를 지키면서&lt;/b&gt; 외국어 영상에 한국어 자막이 필요하신 분들께 도움이 되었으면 합니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/AI &amp;middot; 머신러닝</category>
      <category>KE-T5</category>
      <category>nllb</category>
      <category>pyinstaller</category>
      <category>python</category>
      <category>qwen</category>
      <category>Whisper</category>
      <category>로컬 llm</category>
      <category>오프라인 AI</category>
      <category>자막 생성기</category>
      <category>프라이버시</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/593</guid>
      <comments>https://kwonputer.tistory.com/593#entry593comment</comments>
      <pubDate>Thu, 5 Feb 2026 12:47:14 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Plugin] MediaPipe Face/Pose Landmarker 라이브러리 개발기</title>
      <link>https://kwonputer.tistory.com/587</link>
      <description>&lt;!-- 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;INSTALL&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;kwon_mediapipe_landmarker&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/brA6n5/dJMcacvdqlK/4jD1NuKmxE10WjBnIIHeh1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bdG6BF/dJMcachFw0P/JmUme35zH2LUPQfqExb02K/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cjEnl5/dJMcacvdqlL/XVYz3p6P5QX13F7je7dQTk/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brA6n5/dJMcacvdqlK/4jD1NuKmxE10WjBnIIHeh1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/brA6n5/dJMcacvdqlK/4jD1NuKmxE10WjBnIIHeh1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brA6n5/dJMcacvdqlK/4jD1NuKmxE10WjBnIIHeh1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrA6n5%2FdJMcacvdqlK%2F4jD1NuKmxE10WjBnIIHeh1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;2604&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdG6BF/dJMcachFw0P/JmUme35zH2LUPQfqExb02K/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bdG6BF/dJMcachFw0P/JmUme35zH2LUPQfqExb02K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdG6BF/dJMcachFw0P/JmUme35zH2LUPQfqExb02K/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdG6BF%2FdJMcachFw0P%2FJmUme35zH2LUPQfqExb02K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;2604&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjEnl5/dJMcacvdqlL/XVYz3p6P5QX13F7je7dQTk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cjEnl5/dJMcacvdqlL/XVYz3p6P5QX13F7je7dQTk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjEnl5/dJMcacvdqlL/XVYz3p6P5QX13F7je7dQTk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjEnl5%2FdJMcacvdqlL%2FXVYz3p6P5QX13F7je7dQTk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;2604&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #667eea; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/kwon_mediapipe_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pub.dev&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #212529; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://github.com/KwonGeneral/kwon_mediapipe_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;kwon_mediapipe_landmarker&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter용 MediaPipe Face/Pose Landmarker 플러그인 개발기입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Face Landmarker&lt;/b&gt;는 478개 얼굴 랜드마크와 52개 ARKit 호환 블렌드쉐입을,&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pose Landmarker&lt;/b&gt;는 33개 신체 랜드마크를 감지합니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;iOS 전면 카메라 미러링 해결&lt;/b&gt;, &lt;b&gt;플랫폼별 YUV-RGB 변환 최적화&lt;/b&gt;, &lt;b&gt;통일된 에러 핸들링&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 이 플러그인을 만들었나?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능 소개&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;사용 방법&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;활용 사례&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: WHY --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이 플러그인을 만들었나?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter 생태계에서 &lt;b&gt;MediaPipe 기반의 얼굴/포즈 인식 플러그인&lt;/b&gt;은 존재하지만, 대부분 기본적인 랜드마크 감지만 제공하거나 &lt;b&gt;플랫폼별 동작 차이&lt;/b&gt;가 있는 경우가 많았습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 iOS 전면 카메라에서 좌우가 반대로 인식되는 문제는 많은 개발자들이 겪는 어려움입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;저는 &lt;b&gt;실시간 얼굴 표정 분석&lt;/b&gt;(눈 맞춤, 미소, 긴장도)과 &lt;b&gt;자세 분석&lt;/b&gt;(어깨 대칭, 고개 기울기)이 필요한 프로젝트를 진행하면서, 이러한 기능을 쉽게 사용할 수 있는 플러그인의 필요성을 느꼈습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능 소개&lt;/p&gt;
&lt;!-- Face Landmarker 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;Face Landmarker&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;478개 얼굴 랜드마크 감지&lt;/b&gt; &amp;mdash; 눈, 코, 입, 얼굴 윤곽 등 세밀한 얼굴 특징점을 실시간으로 감지합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;52개 ARKit 호환 블렌드쉐입&lt;/b&gt; &amp;mdash; Apple ARKit과 동일한 블렌드쉐입 체계를 사용하여, eyeBlinkLeft, mouthSmileRight 등의 표정 데이터를 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- Pose Landmarker 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;Pose Landmarker&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;33개 신체 랜드마크&lt;/b&gt; &amp;mdash; MediaPipe Pose 모델 기반으로 전신 자세를 감지합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;World Landmarks 지원&lt;/b&gt; &amp;mdash; 미터 단위의 3D 좌표를 제공하여 실제 공간에서의 자세 분석이 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- Helper Extensions 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Helper Extensions&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;FaceResultHelper &amp;mdash; 14개 분석 메서드&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;eyeContactScore&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;smileScore&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;tensionScore&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;isLookingLeft/Right&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;PoseResultHelper &amp;mdash; 19개 분석 메서드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;postureScore&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;shoulderSymmetryScore&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;headTiltAngle&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;isHandRaised&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;iOS 전면 카메라 미러링 문제&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;iOS 전면 카메라로 촬영하면 이미지가 미러링되어, &lt;b&gt;오른손을 들면 왼손으로 인식&lt;/b&gt;되는 문제가 발생했습니다. Android에서는 정상 동작하지만, iOS에서만 좌우가 반대로 나오는 현상이었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 8px; padding: 16px; margin-bottom: 16px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;해결 시도 1 &amp;mdash; X 좌표 반전&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 &lt;code style=&quot;background-color: #e9ecef; padding: 2px 6px; border-radius: 4px; font-size: 13px;&quot;&gt;x = 1.0 - x&lt;/code&gt;로 X 좌표만 반전했지만, 이것만으로는 부족했습니다. 좌표는 맞아도 &lt;b&gt;랜드마크 인덱스 자체가 여전히 반대&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; X 좌표 반전 + Left/Right 인덱스 스왑&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// iOS - PoseLandmarkerHelper.swift
private static let leftRightSwapMap: [Int: Int] = [
    11: 12, 12: 11,  // leftShoulder &amp;lt;-&amp;gt; rightShoulder
    15: 16, 16: 15,  // leftWrist &amp;lt;-&amp;gt; rightWrist
    // ... 총 16쌍
]

let targetIndex = Self.leftRightSwapMap[originalIndex] ?? originalIndex
landmarksList[targetIndex] = [
    &quot;x&quot;: 1.0 - landmark.x,  // X 좌표 반전
    &quot;y&quot;: landmark.y
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;YUV-RGB 변환 성능 최적화&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;카메라에서 받은 YUV420 이미지를 MediaPipe가 처리할 수 있는 RGB 형식으로 변환해야 하는데, 이 과정이 &lt;b&gt;성능 병목&lt;/b&gt;이 될 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; 플랫폼별 최적화&lt;/p&gt;
&lt;div style=&quot;background-color: #f0fff4; border-radius: 6px; padding: 14px; margin-bottom: 10px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;Android&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;코루틴 기반 병렬 처리로 YUV-RGB 변환 수행&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f0fff4; border-radius: 6px; padding: 14px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;iOS&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;Apple Accelerate 프레임워크의 vImage를 사용하여 하드웨어 가속&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;플랫폼 간 일관된 에러 핸들링&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;Dart, Android(Kotlin), iOS(Swift) 세 플랫폼에서 발생하는 에러를 &lt;b&gt;일관된 방식으로 처리&lt;/b&gt;해야 했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; 통일된 에러 코드 Enum 정의&lt;/p&gt;
&lt;pre class=&quot;crystal&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;enum LandmarkerError {
  notInitialized('NOT_INITIALIZED'),
  modelLoadFailed('MODEL_LOAD_FAILED'),
  invalidImage('INVALID_IMAGE'),
  detectionFailed('DETECTION_FAILED'),
  cameraPermissionDenied('CAMERA_PERMISSION_DENIED'),
  // ... 총 11개 에러 코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: USAGE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;USAGE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;사용 방법&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;1. 설치&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;# pubspec.yaml
dependencies:
  kwon_mediapipe_landmarker: ^0.0.1&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;2. 기본 사용법&lt;/p&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;import 'package:kwon_mediapipe_landmarker/kwon_mediapipe_landmarker.dart';

// Face Landmarker 초기화
final faceLandmarker = FaceLandmarker();
await faceLandmarker.initialize(
  options: FaceLandmarkerOptions(
    numFaces: 1,
    minDetectionConfidence: 0.5,
    outputBlendshapes: true,
  ),
);

// 카메라 이미지에서 감지
final result = await faceLandmarker.detectFromCamera(cameraImage);

// 리소스 해제
await faceLandmarker.close();&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;3. Helper Extension 활용&lt;/p&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// FaceResultHelper 사용
if (faceResult != null) {
  print('눈 맞춤: ${(faceResult.eyeContactScore * 100).toInt()}%');
  print('미소: ${(faceResult.smileScore * 100).toInt()}%');
}

// PoseResultHelper 사용
if (poseResult != null) {
  print('자세 점수: ${(poseResult.postureScore * 100).toInt()}%');
  print('오른손 들기: ${poseResult.isRightHandRaised ? &quot;Yes&quot; : &quot;No&quot;}');
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: USE CASES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;USE CASES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;활용 사례&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;화상 면접 앱&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;눈 맞춤 점수, 자세 점수, 긴장도를 실시간으로 분석하여 면접자에게 피드백 제공&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;헬스케어/피트니스 앱&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;운동 자세 분석, 스쿼트/푸쉬업 카운팅, 자세 교정 가이드&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;온라인 교육 플랫폼&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;학습자의 집중도 분석, 졸음 감지&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;엔터테인먼트&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;AR 필터, 표정 기반 이모지, 아바타 애니메이션&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://developers.google.com/mediapipe/solutions/vision/face_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MediaPipe Face Landmarker&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://developers.google.com/mediapipe/solutions/vision/pose_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MediaPipe Pose Landmarker&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://github.com/KwonGeneral/kwon_mediapipe_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub Repository&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/kwon_mediapipe_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pub.dev&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;MediaPipe의 강력한 얼굴/포즈 인식 기능을 Flutter에서 쉽게 사용할 수 있도록 &lt;b&gt;kwon_mediapipe_landmarker&lt;/b&gt; 플러그인을 개발했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;iOS 전면 카메라 미러링 문제 해결&lt;/b&gt;과 &lt;b&gt;Helper Extension을 통한 편의 기능 제공&lt;/b&gt;에 중점을 두었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;이 플러그인은 화상 면접, 헬스케어, 교육 등 다양한 분야에서 활용될 수 있으며, 앞으로도 지속적으로 기능을 개선해 나갈 예정입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;INSTALL&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;kwon_mediapipe_landmarker&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/peLdC/dJMcad1Tdyj/hcLCzBw6LGYLjG3JjKj0a0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/HABBM/dJMcabXl7uY/DA6gTnBKTHwqbmf0O0BVKk/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ca7ldM/dJMcahXAqDv/QdJVWdZ4QWeQKcNHuLI7Ck/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/peLdC/dJMcad1Tdyj/hcLCzBw6LGYLjG3JjKj0a0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/peLdC/dJMcad1Tdyj/hcLCzBw6LGYLjG3JjKj0a0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/peLdC/dJMcad1Tdyj/hcLCzBw6LGYLjG3JjKj0a0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpeLdC%2FdJMcad1Tdyj%2FhcLCzBw6LGYLjG3JjKj0a0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;2604&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HABBM/dJMcabXl7uY/DA6gTnBKTHwqbmf0O0BVKk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/HABBM/dJMcabXl7uY/DA6gTnBKTHwqbmf0O0BVKk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HABBM/dJMcabXl7uY/DA6gTnBKTHwqbmf0O0BVKk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHABBM%2FdJMcabXl7uY%2FDA6gTnBKTHwqbmf0O0BVKk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;2604&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca7ldM/dJMcahXAqDv/QdJVWdZ4QWeQKcNHuLI7Ck/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ca7ldM/dJMcahXAqDv/QdJVWdZ4QWeQKcNHuLI7Ck/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca7ldM/dJMcahXAqDv/QdJVWdZ4QWeQKcNHuLI7Ck/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca7ldM%2FdJMcahXAqDv%2FQdJVWdZ4QWeQKcNHuLI7Ck%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;2604&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #667eea; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/kwon_mediapipe_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pub.dev&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #212529; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://github.com/KwonGeneral/kwon_mediapipe_landmarker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;처음 이 플러그인을 개발하기 시작했을 때는 단순히 MediaPipe를 Flutter에서 사용하는 것이 목표였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 개발을 진행하면서 iOS 미러링 문제, 성능 최적화, 에러 핸들링 등 예상치 못한 도전들을 마주하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 iOS에서 &quot;오른손을 들었는데 왼손으로 인식되는&quot; 문제를 해결하기 위해 X 좌표 반전뿐만 아니라 &lt;b&gt;랜드마크 인덱스까지 스왑&lt;/b&gt;해야 한다는 것을 알아냈을 때, 플랫폼별 차이를 깊이 이해하는 것이 얼마나 중요한지 다시 한번 깨달았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;이 플러그인이 Flutter로 얼굴/포즈 인식 앱을 개발하시는 분들께 작은 도움이 되었으면 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;피드백이나 개선 사항은 GitHub Issue로 남겨주시면 감사하겠습니다!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/프로젝트</category>
      <category>Android</category>
      <category>flutter</category>
      <category>ios</category>
      <category>MediaPipe</category>
      <category>pub.dev</category>
      <category>모바일AI</category>
      <category>얼굴인식</category>
      <category>크로스플랫폼</category>
      <category>포즈인식</category>
      <category>플러그인개발</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/587</guid>
      <comments>https://kwonputer.tistory.com/587#entry587comment</comments>
      <pubDate>Mon, 2 Feb 2026 15:29:11 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 몽땅계산기 - 한국 계산기 앱 개발기</title>
      <link>https://kwonputer.tistory.com/592</link>
      <description>&lt;!-- 스토어 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;몽땅 계산기 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/Sukbz/dJMcahQOixJ/j00NZ0wVlkzjt9GNHEBDU1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/czUM94/dJMcabJQVTT/7pz949eHWkGMMVCeMZ91x0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bRhXg0/dJMcahQOixK/tcAklqlNPrpncKuDKk59vk/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bby5MX/dJMcahQOixL/mpsf4DKJZMrDuNj43N3pNK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/rDDRs/dJMcacWfNUW/v6FkI8KT9YYEUq95BenVek/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sukbz/dJMcahQOixJ/j00NZ0wVlkzjt9GNHEBDU1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/Sukbz/dJMcahQOixJ/j00NZ0wVlkzjt9GNHEBDU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sukbz/dJMcahQOixJ/j00NZ0wVlkzjt9GNHEBDU1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSukbz%2FdJMcahQOixJ%2Fj00NZ0wVlkzjt9GNHEBDU1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czUM94/dJMcabJQVTT/7pz949eHWkGMMVCeMZ91x0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/czUM94/dJMcabJQVTT/7pz949eHWkGMMVCeMZ91x0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czUM94/dJMcabJQVTT/7pz949eHWkGMMVCeMZ91x0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczUM94%2FdJMcabJQVTT%2F7pz949eHWkGMMVCeMZ91x0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRhXg0/dJMcahQOixK/tcAklqlNPrpncKuDKk59vk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bRhXg0/dJMcahQOixK/tcAklqlNPrpncKuDKk59vk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRhXg0/dJMcahQOixK/tcAklqlNPrpncKuDKk59vk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRhXg0%2FdJMcahQOixK%2FtcAklqlNPrpncKuDKk59vk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bby5MX/dJMcahQOixL/mpsf4DKJZMrDuNj43N3pNK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bby5MX/dJMcahQOixL/mpsf4DKJZMrDuNj43N3pNK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bby5MX/dJMcahQOixL/mpsf4DKJZMrDuNj43N3pNK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbby5MX%2FdJMcahQOixL%2Fmpsf4DKJZMrDuNj43N3pNK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDDRs/dJMcacWfNUW/v6FkI8KT9YYEUq95BenVek/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/rDDRs/dJMcacWfNUW/v6FkI8KT9YYEUq95BenVek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDDRs/dJMcacWfNUW/v6FkI8KT9YYEUq95BenVek/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDDRs%2FdJMcacWfNUW%2Fv6FkI8KT9YYEUq95BenVek%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.taxcalc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6756904490&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;몽땅계산기 (TaxCalc)&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;한국 세법과 제도에 특화된 &lt;b&gt;100개+ 고품질 계산기&lt;/b&gt;를&amp;nbsp;제공하는 Flutter 모바일 앱입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Clean Architecture + GetX GetBuilder.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;연도별 세율 데이터(2024/2025/2026), PDF 리포트, Drift SQLite, Firebase.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;100개+ 계산기 품질 표준화&lt;/b&gt;, &lt;b&gt;연도별 법령 데이터 중앙 관리&lt;/b&gt;, &lt;b&gt;DI Singleton 최적화&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 이 앱을 만들었나?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능 소개&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;아키텍처&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;활용 사례&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: WHY --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이 앱을 만들었나?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;세금 계산, 급여 계산, 대출 이자... &lt;b&gt;매년 바뀌는 한국 법령&lt;/b&gt;에 따라 정확한 계산이 필요한 순간이 많습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 대부분의 계산기 앱은 &lt;b&gt;단순 곱셈&lt;/b&gt; 수준이거나, &lt;b&gt;오래된 세율&lt;/b&gt;을 사용하거나, &lt;b&gt;한국 법령의 특수 조건&lt;/b&gt;(누진세율, 공제, 감면 등)을 반영하지 않는 경우가 많았습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 종합소득세는 &lt;b&gt;8구간 누진세율&lt;/b&gt;을 적용해야 하고, 양도소득세는 &lt;b&gt;장기보유특별공제&lt;/b&gt;와 &lt;b&gt;1세대1주택 비과세&lt;/b&gt; 조건을 고려해야 합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;저는 &lt;b&gt;실제 한국 법령을 정확히 반영&lt;/b&gt;하면서도, &lt;b&gt;매년 법령 변경 시 쉽게 업데이트&lt;/b&gt;할 수 있는 계산기 앱을 만들고 싶었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능 소개&lt;/p&gt;
&lt;!-- 기능 1: 101개 계산기 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;100개+ 고품질 계산기&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세금&lt;/b&gt;&amp;nbsp;&amp;mdash; 종합소득세, 양도소득세, 취득세, 법인세, 부가가치세, 증여세, 상속세, 연말정산 등&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;급여&lt;/b&gt;&amp;mdash; 실수령액, 최저임금, 퇴직금, 야근수당, 주휴수당, 연차수당, 실업급여 등&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부동산&lt;/b&gt;&amp;nbsp;&amp;mdash; 전세vs매매, 중개수수료, 종합부동산세, LTV/DTI/DSR, 청약가점 등&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;금융&lt;/b&gt;&amp;nbsp;&amp;mdash; 복리이자, 대출상환, 투자수익률, 배당수익률, 환율계산 등&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기타&lt;/b&gt; &amp;mdash; 건강, 자동차, 교육, 사업, 육아, 군대, 상속, 노후&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 2: 연도별 세율 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;연도별 세율 데이터 시스템 (2024/2025/2026)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소득세 8구간 누진세율&lt;/b&gt; &amp;mdash; 1,400만원 이하 6% ~ 10억원 초과 45%&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4대보험 요율 (2025)&lt;/b&gt; &amp;mdash; 건강보험 7.09%, 장기요양 12.95%, 국민연금 9%&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최저임금&lt;/b&gt; &amp;mdash; 2024년 9,860원, 2025년 10,030원&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;법인세 4구간 세율&lt;/b&gt; &amp;mdash; 2억원 이하 9% ~ 3,000억원 초과 24%&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 3: 실제 법령 기반 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;실제 한국 법령 기반 계산 로직&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;종합소득세&lt;/b&gt; &amp;mdash; 8구간 누진세율, 소득유형별 공제, 세액공제 적용&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;양도소득세&lt;/b&gt; &amp;mdash; 장기보유특별공제(최대 80%), 1세대1주택 비과세 판정&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;취득세&lt;/b&gt; &amp;mdash; 다주택자 중과(8~12%), 생애최초 감면(200만원 한도)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상세 breakdown&lt;/b&gt; &amp;mdash; 계산 과정 단계별 표시 (어떻게 이 금액이 나왔는지 투명하게 공개)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 4: 다양한 계산 패턴 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e03131; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;일괄/연계/역산/비교 계산 패턴&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비교 계산&lt;/b&gt; &amp;mdash; 전세 vs 매매 5년 비용 비교, 리스 vs 구매, 법인 vs 개인사업자&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연계 계산&lt;/b&gt; &amp;mdash; A 계산 결과를 B 계산의 입력으로 자동 연결&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역산 계산&lt;/b&gt; &amp;mdash; 목표 실수령액에서 필요 연봉 역산&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부채 상환 전략&lt;/b&gt; &amp;mdash; 눈사태(고금리 우선) vs 눈덩이(소액 우선) 비교&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기능 5: PDF/공유 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #868e96; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;PDF 리포트 생성 + 공유&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PDF 문서 생성&lt;/b&gt; &amp;mdash; 계산 결과 + 입력값 + breakdown을 PDF로 저장&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스크린샷 캡처&lt;/b&gt; &amp;mdash; 계산 결과 화면을 이미지로 저장&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타 앱 공유&lt;/b&gt; &amp;mdash; 카카오톡, 메일 등으로 바로 공유&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: ARCHITECTURE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;아키텍처&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: 600; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Clean Architecture + GetX GetBuilder&lt;/p&gt;
&lt;pre class=&quot;crystal&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;lib/
├── presenter/              # Presentation Layer
│   ├── pages/calculator/   # 101개 계산기 UI + ViewModel
│   │   ├── tax/           # 세금 15개
│   │   ├── salary/        # 급여 18개
│   │   ├── realEstate/    # 부동산 13개
│   │   └── ...
│   ├── widgets/           # TCScaffold, TCButton, TCNumberTextField
│   └── styles/            # RColors, RTexts, AppTheme
│
├── domain/                 # Domain Layer
│   └── model/calculator/
│       ├── CalculatorType.dart  # 101개 enum (5개 getter)
│       └── CalculationResultModel.dart
│
├── data/                   # Data Layer
│   ├── services/
│   │   ├── calculators/   # 103개 @singleton Calculator 서비스
│   │   └── tax_rates/     # TaxRate2024, TaxRate2025, TaxRate2026
│   └── database/          # Drift SQLite ORM
│
└── di/                     # GetIt + Injectable 자동 DI&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- BaseViewModel 패턴 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;BaseViewModel 템플릿 메서드 패턴&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;113개 ViewModel이 모두 &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;BaseViewModel&lt;/code&gt;을 상속하여 &lt;b&gt;일관된 라이프사이클&lt;/b&gt;과 &lt;b&gt;상태 관리&lt;/b&gt;를 공유합니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;abstract class BaseViewModel extends GetxController {
  bool isInitLoading = true;
  bool isLoading = false;
  bool isError = false;
  String? errorMessage;

  @override
  void onReady() {
    super.onReady();
    initialize();  // 템플릿 메서드 패턴
  }

  Future&amp;lt;void&amp;gt; initialize() async {
    await init();  // 서브클래스에서 구현
    isInitLoading = false;
    update(updateKeys);  // 선택적 리빌드
  }

  Future&amp;lt;void&amp;gt; init();
  List&amp;lt;String&amp;gt; get updateKeys;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;100개+ 계산기의 일관된 품질 유지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;계산기가 각각 다른 법령/제도를 다루면서도 &lt;b&gt;일관된 UI 패턴&lt;/b&gt;, &lt;b&gt;코드 구조&lt;/b&gt;, &lt;b&gt;계산 정확성&lt;/b&gt;을 유지해야 했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 더 많은 계산기가 있었지만, 상당수가 &lt;b&gt;단순 곱셈 수준의 저품질&lt;/b&gt;이었고, 법령 데이터가 산발적으로 하드코딩되어 있었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; 품질 표준화 + 저품질 계산기 삭제&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 저품질 삭제&lt;/b&gt; &amp;mdash; 160개+ &amp;rarr; 100개+, 품질 기준 미달 계산기 과감히 제거&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 계산기 생성 10단계 체크리스트&lt;/b&gt; &amp;mdash; Enum 타입 시스템, 실제 계산 로직, 상세 breakdown, 법적 근거 주석 필수화&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. dart analyze 경고 0개 유지&lt;/b&gt; &amp;mdash; CI/CD에서 경고 발생 시 빌드 실패 처리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. BaseViewModel 상속 강제&lt;/b&gt; &amp;mdash; ViewModel 모두 동일 패턴 적용&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;연도별 법령 데이터 중앙 관리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;매년 1월 법령이 변경되면, &lt;b&gt;수십 개 계산기&lt;/b&gt;에 흩어진 세율/공제액/요율을 찾아서 업데이트해야 했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 각 계산기 서비스에 &lt;b&gt;하드코딩&lt;/b&gt;되어 있어서, 한 곳을 수정하면 다른 곳을 누락하는 실수가 발생했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; TaxRateProvider 중앙 집중 관리&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// data/services/tax_rates/
├── TaxRateProvider.dart     // @singleton, 연도 선택 로직
├── TaxRate2024.dart         // 2024년 세율 데이터
├── TaxRate2025.dart         // 2025년 세율 데이터
└── TaxRate2026.dart         // 2026년 세율 데이터

// 사용 예시
final taxRate = getIt&amp;lt;TaxRateProvider&amp;gt;().getRate(2025);
final incomeTax = taxRate.calculateIncomeTax(income);  // 8구간 누진세율 자동 적용&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-top: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;: 새 연도 세율 추가 시 &lt;b&gt;TaxRate2027.dart 1개 파일만 추가&lt;/b&gt;하면 전체 계산기에 반영&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;@singleton DI 등록과 메모리 관리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Calculator 서비스 + UseCase + 공통 서비스들, &lt;b&gt;singleton&lt;/b&gt;을 DI 등록해야 했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;모든 singleton이 앱 시작 시 생성되면 &lt;b&gt;메모리 부담&lt;/b&gt;과 &lt;b&gt;초기화 지연&lt;/b&gt;이 발생할 수 있었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; Injectable 코드 생성 + Lazy Singleton&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// Calculator 서비스 예시
@singleton  // GetIt에 Lazy Singleton으로 자동 등록
class IncomeTaxCalculator {
  final TaxRateProvider _taxRateProvider;

  IncomeTaxCalculator(this._taxRateProvider);  // DI 자동 주입

  CalculationResult calculate(int income, int year) {
    final taxRate = _taxRateProvider.getRate(year);
    // 8구간 누진세율 계산...
  }
}

// build_runner로 자동 생성
// injection.config.dart에 142개 서비스 등록 코드 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-top: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;: 실제 사용 시점에만 인스턴스 생성 &amp;rarr; &lt;b&gt;앱 시작 시간 영향 없음&lt;/b&gt;, 새 계산기 추가 시 &lt;code style=&quot;background-color: #e9ecef; padding: 2px 6px; border-radius: 4px; font-size: 13px;&quot;&gt;@singleton&lt;/code&gt; 어노테이션만 추가하면 자동 등록&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: TECH STACK --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TECH STACK&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언어/프레임워크&lt;/b&gt; &amp;mdash; Dart 3.1+, Flutter 3.38.5&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아키텍처&lt;/b&gt; &amp;mdash; Clean Architecture (presenter/domain/data)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태관리&lt;/b&gt; &amp;mdash; GetX 4.6.6 (GetBuilder 패턴, .obs 미사용)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI&lt;/b&gt; &amp;mdash; GetIt 8.0.3 + Injectable 2.5.0 (코드 생성)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬 DB&lt;/b&gt; &amp;mdash; Drift 2.20.3 (SQLite ORM, 3테이블)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PDF/공유&lt;/b&gt; &amp;mdash; pdf 3.11.1, printing 5.13.4, screenshot 3.0.0, share_plus 10.1.4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Firebase&lt;/b&gt; &amp;mdash; Analytics, Crashlytics, Remote Config, Messaging, Core (5종)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폰트&lt;/b&gt; &amp;mdash; Pretendard (9웨이트: Thin ~ Black)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 6: USE CASES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;USE CASES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;활용 사례&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;직장인 연말정산&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;연말정산 예상 환급액 계산, 소득공제/세액공제 항목별 시뮬레이션&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;부동산 투자 의사결정&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;전세 vs 매매 5년 비용 비교, 취득세/양도소득세 시뮬레이션, LTV/DTI/DSR 한도 계산&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;창업자/사업자&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;법인 vs 개인사업자 세금 비교, 일반과세 vs 간이과세 비교, 부가가치세 계산&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin-bottom: 12px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;퇴직 준비&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;퇴직금 계산, 퇴직연금 일시금 vs 연금 비교, 실업급여 수령액/기간 계산&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;상속/증여 계획&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;상속세/증여세 시뮬레이션, 상속 순위 및 지분 계산&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://www.nts.go.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;국세청&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://www.law.go.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;국가법령정보센터&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://www.4insure.or.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;4대사회보험 정보연계센터&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/drift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Drift (SQLite ORM)&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/injectable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Injectable&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;몽땅계산기&lt;/b&gt;는 한국 세법과 제도를 정확히 반영한 &lt;b&gt;100개+ 고품질 계산기&lt;/b&gt;를 제공하는 Flutter 앱입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Clean Architecture&lt;/b&gt;와 &lt;b&gt;BaseViewModel 템플릿 패턴&lt;/b&gt;으로 일관되게 유지했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;연도별 세율 데이터 중앙 관리&lt;/b&gt; 시스템을 통해, 매년 법령 변경 시에도 빠르게 대응할 수 있는 구조를 만들었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트를 통해 &lt;b&gt;대규모 Flutter 앱의 아키텍처 설계&lt;/b&gt;, &lt;b&gt;DI 최적화&lt;/b&gt;, &lt;b&gt;도메인 지식(한국 세법)과 기술의 결합&lt;/b&gt;에 대해 깊이 경험할 수 있었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 스토어 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;몽땅 계산기 다운로드 &lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: center;&quot;&gt;(AOS는 베타 테스트 중입니다)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/MNIye/dJMcagxz722/fxaEEEMg2CnZaKkuiokTXK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/oCdbb/dJMcagxz723/5LlpkYBKWnYRFkcmM2KiS1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/9zF1h/dJMcagK9orr/2F7rek9hfLL4Mfujn9Ua8k/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ZYieY/dJMcabwhiWQ/SdAGNWk8uTYtEVN8UW7mV0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bNSm6V/dJMcabwhiWR/CUWMLzAmmN54hUUp3GwOa1/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MNIye/dJMcagxz722/fxaEEEMg2CnZaKkuiokTXK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/MNIye/dJMcagxz722/fxaEEEMg2CnZaKkuiokTXK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MNIye/dJMcagxz722/fxaEEEMg2CnZaKkuiokTXK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMNIye%2FdJMcagxz722%2FfxaEEEMg2CnZaKkuiokTXK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oCdbb/dJMcagxz723/5LlpkYBKWnYRFkcmM2KiS1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/oCdbb/dJMcagxz723/5LlpkYBKWnYRFkcmM2KiS1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oCdbb/dJMcagxz723/5LlpkYBKWnYRFkcmM2KiS1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoCdbb%2FdJMcagxz723%2F5LlpkYBKWnYRFkcmM2KiS1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9zF1h/dJMcagK9orr/2F7rek9hfLL4Mfujn9Ua8k/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/9zF1h/dJMcagK9orr/2F7rek9hfLL4Mfujn9Ua8k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9zF1h/dJMcagK9orr/2F7rek9hfLL4Mfujn9Ua8k/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9zF1h%2FdJMcagK9orr%2F2F7rek9hfLL4Mfujn9Ua8k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZYieY/dJMcabwhiWQ/SdAGNWk8uTYtEVN8UW7mV0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ZYieY/dJMcabwhiWQ/SdAGNWk8uTYtEVN8UW7mV0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZYieY/dJMcabwhiWQ/SdAGNWk8uTYtEVN8UW7mV0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZYieY%2FdJMcabwhiWQ%2FSdAGNWk8uTYtEVN8UW7mV0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNSm6V/dJMcabwhiWR/CUWMLzAmmN54hUUp3GwOa1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bNSm6V/dJMcabwhiWR/CUWMLzAmmN54hUUp3GwOa1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNSm6V/dJMcabwhiWR/CUWMLzAmmN54hUUp3GwOa1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNSm6V%2FdJMcabwhiWR%2FCUWMLzAmmN54hUUp3GwOa1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;3120&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.taxcalc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6756904490&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;처음 이 앱을 시작했을 때는 &quot;간단한 세금 계산기&quot;를 목표로 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 한국 세법을 깊이 파고들수록, &lt;b&gt;누진세율&lt;/b&gt;, &lt;b&gt;장기보유특별공제&lt;/b&gt;, &lt;b&gt;다주택자 중과&lt;/b&gt;, &lt;b&gt;두루누리 감면&lt;/b&gt; 등 복잡한 조건들을 정확히 반영해야 한다는 것을 깨달았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 160개+ 중 저품질 계산기를 삭제하고, &lt;b&gt;100개+ 고품질 계산기&lt;/b&gt;만 남기는 어려운 결정을 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;양보다 질&quot;이라는 원칙을 지키면서, 사용자에게 &lt;b&gt;신뢰할 수 있는 계산 결과&lt;/b&gt;를 제공하는 것이 더 가치 있다고 판단했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;이 앱이 세금 계산, 재무 계획을 고민하시는 분들께 작은 도움이 되었으면 합니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/프로젝트</category>
      <category>Clean Architecture</category>
      <category>Drift SQLite</category>
      <category>firebase</category>
      <category>flutter</category>
      <category>getx</category>
      <category>injectable</category>
      <category>PDF 생성</category>
      <category>대규모 앱</category>
      <category>세금 계산기</category>
      <category>한국 세법</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/592</guid>
      <comments>https://kwonputer.tistory.com/592#entry592comment</comments>
      <pubDate>Mon, 2 Feb 2026 15:13:51 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AI 내러티브 게임 서버리스 백엔드 - 메모리 시스템 + DynamoDB</title>
      <link>https://kwonputer.tistory.com/591</link>
      <description>&lt;!-- 스토어 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;힘내라 권대리 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dftUum/dJMcaajQHFk/U4xorV2kwNBnSjjAnVR7DK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/QfjxU/dJMcaaqCu9m/6P3RiNsQWvr4OXKaSFyZu0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/daAffl/dJMcaajQHFj/iOcJPPCcuWBTxFuf1NLLy0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/GOqFL/dJMcadHBrGx/gCLLWsoKK8jsSKCAIExS81/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bRRauL/dJMcaajQHFl/pIz04P2OFuDOx7SMJOOZqk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/Mqtda/dJMcadHBrGy/NCf3B96rexfD9wgjyNoDR0/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dftUum/dJMcaajQHFk/U4xorV2kwNBnSjjAnVR7DK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dftUum/dJMcaajQHFk/U4xorV2kwNBnSjjAnVR7DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dftUum/dJMcaajQHFk/U4xorV2kwNBnSjjAnVR7DK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdftUum%2FdJMcaajQHFk%2FU4xorV2kwNBnSjjAnVR7DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QfjxU/dJMcaaqCu9m/6P3RiNsQWvr4OXKaSFyZu0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/QfjxU/dJMcaaqCu9m/6P3RiNsQWvr4OXKaSFyZu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QfjxU/dJMcaaqCu9m/6P3RiNsQWvr4OXKaSFyZu0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQfjxU%2FdJMcaaqCu9m%2F6P3RiNsQWvr4OXKaSFyZu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daAffl/dJMcaajQHFj/iOcJPPCcuWBTxFuf1NLLy0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/daAffl/dJMcaajQHFj/iOcJPPCcuWBTxFuf1NLLy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daAffl/dJMcaajQHFj/iOcJPPCcuWBTxFuf1NLLy0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaAffl%2FdJMcaajQHFj%2FiOcJPPCcuWBTxFuf1NLLy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GOqFL/dJMcadHBrGx/gCLLWsoKK8jsSKCAIExS81/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/GOqFL/dJMcadHBrGx/gCLLWsoKK8jsSKCAIExS81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GOqFL/dJMcadHBrGx/gCLLWsoKK8jsSKCAIExS81/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGOqFL%2FdJMcadHBrGx%2FgCLLWsoKK8jsSKCAIExS81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRRauL/dJMcaajQHFl/pIz04P2OFuDOx7SMJOOZqk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bRRauL/dJMcaajQHFl/pIz04P2OFuDOx7SMJOOZqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRRauL/dJMcaajQHFl/pIz04P2OFuDOx7SMJOOZqk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRRauL%2FdJMcaajQHFl%2FpIz04P2OFuDOx7SMJOOZqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mqtda/dJMcadHBrGy/NCf3B96rexfD9wgjyNoDR0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/Mqtda/dJMcadHBrGy/NCf3B96rexfD9wgjyNoDR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mqtda/dJMcadHBrGy/NCf3B96rexfD9wgjyNoDR0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMqtda%2FdJMcadHBrGy%2FNCf3B96rexfD9wgjyNoDR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonbackgame.kwondaeri&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6753064792&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;힘내라 권대리 - AI 내러티브 게임 서버리스 백엔드&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;AWS CDK + Lambda + DynamoDB + GPT로 구축한 AI 턴제 내러티브 게임 백엔드 개발기입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;매 턴마다 GPT가 직장인의 삶을 시뮬레이션하는 스토리를 생성하고, 플레이어의 선택에 따라 이야기가 분기됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;계층적 메모리 시스템&lt;/b&gt;, &lt;b&gt;DynamoDB Single Table Design&lt;/b&gt;, &lt;b&gt;AI 응답 안정화&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;프로젝트 소개&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;AWS 아키텍처&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;AI 파이프라인&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;핵심 코드&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: PROJECT --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;PROJECT&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 소개&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힘내라 권대리&lt;/b&gt;는 한국 직장인의 삶을 시뮬레이션하는 AI 기반 턴제 내러티브 게임입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;플레이어는 '권대리'가 되어 매일 벌어지는 직장 내 상황에 대응하고, 체력/스트레스/피로도 3가지 스탯을 관리하며 생존해야 합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;매 턴마다 &lt;b&gt;GPT-4.1-mini&lt;/b&gt;가 이전 선택과 현재 상태를 반영한 새로운 에피소드를 생성하고, 플레이어에게 2-3개의 선택지를 제시합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;스탯이 한계에 도달하면 &lt;b&gt;과로사&lt;/b&gt;, &lt;b&gt;번아웃&lt;/b&gt;, &lt;b&gt;실직&lt;/b&gt; 등의 엔딩을 맞이하게 됩니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 기술 스택 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IaC&lt;/b&gt; &amp;mdash; AWS CDK v2 (TypeScript)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;런타임&lt;/b&gt; &amp;mdash; Node.js 18.x (AWS Lambda)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스&lt;/b&gt; &amp;mdash; DynamoDB (Single Table Design, 2 GSI)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI&lt;/b&gt; &amp;mdash; OpenAI GPT-4.1-mini (Structured Outputs)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인증&lt;/b&gt; &amp;mdash; Kakao OAuth + JWT&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빌드&lt;/b&gt; &amp;mdash; Webpack 5 (멀티 엔트리 Lambda 번들링)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: ARCHITECTURE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;AWS 서버리스 아키텍처&lt;/p&gt;
&lt;!-- 아키텍처 다이어그램 --&gt;
&lt;div style=&quot;background-color: #1e1e1e; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #d4d4d4; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                    AWS ap-northeast-2 (Seoul)                │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              API Gateway (REST)                       │   │
│  │              kwonbackgame-api / Stage: dev            │   │
│  └──────────┬──────────┬──────────┬──────────────────┘   │
│             |          |          |          |            │
│        ┌────┴───┐ ┌────┴───┐ ┌────┴─────┐ ┌──┴────────┐ │
│        │  Game  │ │  User  │ │Character │ │Announce-  │ │
│        │ Lambda │ │ Lambda │ │  Lambda  │ │ment Lambda│ │
│        │ 512MB  │ │ 256MB  │ │  512MB   │ │  256MB    │ │
│        └───┬────┘ └───┬────┘ └────┬─────┘ └────┬──────┘ │
│            |          |           |             |        │
│        ┌───┴──────────┴───────────┴─────────────┴───┐    │
│        │           Lambda Layer (CommonLayer)        │    │
│        │           Node.js 18.x 공통 의존성           │    │
│        └────────────────────┬────────────────────────┘   │
│                             |                            │
│        ┌────────────────────┼────────────────────┐       │
│        v                    v                    v       │
│  ┌───────────┐    ┌──────────────┐     ┌──────────┐     │
│  │ DynamoDB  │    │   Secrets    │     │  OpenAI  │     │
│  │ Single    │    │   Manager    │     │  GPT API │     │
│  │ Table     │    │ - OpenAI Key │     │gpt-4.1   │     │
│  │ 2 GSI     │    │ - Kakao Key  │     │  -mini   │     │
│  └───────────┘    └──────────────┘     └──────────┘     │
└─────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- Lambda 함수 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;4개 Lambda 함수&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GameFunction&lt;/b&gt; &amp;mdash; 게임 세션/턴 처리, GPT 호출, 스탯 계산, 사망 판정&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserFunction&lt;/b&gt; &amp;mdash; Kakao/Apple 로그인, 프로필 관리, 포인트/출석 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CharacterFunction&lt;/b&gt; &amp;mdash; GPT 기반 캐릭터 AI 생성, 캐릭터 CRUD&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AnnouncementFunction&lt;/b&gt; &amp;mdash; 공지사항, 앱 상태, 강제 업데이트 관리&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- DynamoDB 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;DynamoDB Single Table Design&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6가지 엔티티&lt;/b&gt; &amp;mdash; User, Character, Session, Turn, Point, Announcement를 하나의 테이블에 저장&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI1&lt;/b&gt; &amp;mdash; 이메일/Access Token/사용자 기반 조회&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI2&lt;/b&gt; &amp;mdash; SNS 토큰/Refresh Token/활성 세션 기반 조회&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 키 패턴 코드 블록 --&gt;
&lt;p style=&quot;font-size: 15px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;키 패턴 설계&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// 엔티티별 PK/SK 패턴
User:        PK=USER#{id}       SK=PROFILE
Session:     PK=SESSION#{id}    SK=METADATA#{id}
Turn:        PK=SESSION#{id}    SK=TURN#00001  (5자리 패딩)
Character:   PK=CHAR#{id}       SK=METADATA#{id}
Point:       PK=USER#{id}       SK=POINT#{id}
Announcement: PK=ANNOUNCE#{id}  SK=METADATA#{id}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: AI PIPELINE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;AI PIPELINE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 내러티브 생성 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 선택지를 고르면, 백엔드는 이전 턴들의 히스토리를 분석하고 GPT에게 다음 에피소드 생성을 요청합니다.&lt;/p&gt;
&lt;!-- 파이프라인 다이어그램 --&gt;
&lt;div style=&quot;background-color: #1e1e1e; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;pre class=&quot;less&quot; style=&quot;color: #d4d4d4; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;[Flutter 앱]
    |
    | POST /game/turn
    | { sessionId, selectedChoiceText }
    v
[API Gateway] --&amp;gt; [Game Lambda]
                       |
                  [TurnProcessorService]
                       |
           +-----------+-----------+
           |           |           |
     [DynamoDB에서]  [3-Level]  [사망 임박]
     [최근 8턴 로드]  [Memory]   [체크]
           |         [구축]        |
           +-----------+-----------+
                       |
              [프롬프트 구성]
              System: XML (캐릭터/규칙/시공간)
              User: 메모리 컨텍스트 + 선택 행동
                       |
              [GPT-4.1-mini API]
              Structured Outputs (JSON Schema)
              temperature: 0.72
                       |
              [응답 후처리]
              ResponseSafeParser + NameNormalizer
                       |
              [TurnResult 반환]
              에피소드 + 선택지 + 스탯 + 이미지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 메모리 시스템 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;3단계 계층적 메모리 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT의 토큰 한계(4K-16K) 내에서 장기 내러티브 일관성을 유지하기 위한 핵심 설계입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Level 1: Immediate Memory (T0)&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;가장 최근 1턴 &amp;mdash; 전체 에피소드 텍스트 + 모든 메타데이터&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Level 2: Recent Memory (T1-T2)&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;최근 2-3턴 &amp;mdash; 40-60음절 요약 + 메타데이터&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;Level 3: Context Memory (T3-T7)&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;이전 5턴 &amp;mdash; 메타데이터만 (감정/캐릭터/위치/시간/활동)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 메모리 다이어그램 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;정보량 시각화&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot; style=&quot;font-size: 13px; color: #495057; white-space: pre-wrap;&quot;&gt;&lt;code&gt;턴 순서:  T7  T6  T5  T4  T3  T2  T1  T0  현재
          |---|---|---|---|---|---|---|---|---&amp;gt;
          Context (메타만)  Recent   Imm.  New

GPT에 전달되는 정보량:
  T0: ████████████████████████ (전체 텍스트)
  T1: ██████████████           (요약)
  T2: ██████████████           (요약)
  T3-T7: ████████              (메타데이터만)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT 토큰 한도 내에서 장기 내러티브 일관성 유지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;턴이 30회 이상 진행되면 이전 모든 에피소드를 프롬프트에 포함할 수 없어 캐릭터 관계나 이전 사건의 연속성이 끊기는 문제가 발생했습니다. 단순 truncation은 중요한 맥락 손실을 초래합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; 3단계 계층적 메모리 시스템&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// turn-processor.service.ts
private buildLayeredMemory(recentTurns: GameTurn[]): LayeredMemory {
  const immediate = this.buildImmediateMemory(recentTurns[0]); // T0: 전체 텍스트
  const recent = recentTurns.slice(1, 3).map(t =&amp;gt;
    this.buildRecentMemory(t)  // T1-T2: 요약
  );
  const context = recentTurns.slice(3, 8).map(t =&amp;gt;
    this.buildContextMemory(t) // T3-T7: 메타데이터만
  );

  return { immediate, recent, context, subCharacterCount };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; padding-top: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;30턴 이상 장기 게임에서도 캐릭터 관계와 스토리 일관성을 유지하면서 토큰 사용량을 효율적으로 관리할 수 있게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 생성 JSON 응답의 파싱 안정성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT가 생성한 게임 턴 결과(에피소드, 선택지, 스탯 변화, 감정)를 JSON으로 파싱할 때 형식 불일치, 누락 필드, 잘못된 타입 등으로 런타임 에러가 발생했습니다. 특히 한국어 텍스트와 JSON 구조가 혼재할 때 파싱 실패 빈도가 증가했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; ResponseSafeParser + Structured Outputs&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// response-safe-parser.ts
class ResponseSafeParser {
  parseText(value: any, defaultVal: string): string { ... }
  parseSummary(value: any): string {
    // 20자 미만 -&amp;gt; 기본값, 100자 초과 -&amp;gt; 잘라냄
  }
  parseEmotion(value: any): EmotionType {
    // 5개 enum 검증 (기본값: &quot;tired&quot;)
  }
  parseSingleValue(value: any): number {
    // 0-25 범위 클램핑
  }
  parseChoices(value: any): Choice[] {
    // 2-4개 제한, 기본값 2개
  }
}

// OpenAI Structured Outputs (JSON Schema strict: true)
response_format: {
  type: &quot;json_schema&quot;,
  json_schema: { strict: true, schema: ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; padding-top: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;JSON 파싱 성공률 99% 이상 달성. 실패 시에도 기본값으로 게임 진행이 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터 이름/직급 호칭의 불일치&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT가 생성한 텍스트에서 같은 캐릭터를 &quot;이수연 사원&quot;, &quot;수연 씨&quot;, &quot;이 사원님&quot; 등 다양한 형태로 호칭하여 일관성이 떨어지고, 대화문/서술문에서 존댓말 규칙이 혼란스러웠습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; CharacterNameNormalizer (10단계 치환)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// character-name-normalizer.ts
class CharacterNameNormalizer {
  normalizeText(text: string, context: 'episode' | 'summary' | 'choice'): string {
    if (context === 'episode') {
      // 따옴표로 대화문/서술문 분리
      const parts = this.splitByQuotes(text);
      return parts.map(p =&amp;gt; p.isDialogue
        ? this.normalizeWithHonorific(p.text)    // &quot;이수연 사원님&quot;
        : this.normalizeWithoutHonorific(p.text) // &quot;이수연 사원&quot;
      ).join('');
    }
    return this.normalizeWithoutHonorific(text);
  }

  // 10단계 치환 규칙
  // 1. 풀네임+직급+호칭 (&quot;이수연 사원 씨&quot; -&amp;gt; &quot;이수연 사원님&quot;)
  // 2. 이름+직급+호칭 (&quot;수연 사원 씨&quot; -&amp;gt; &quot;이수연 사원님&quot;)
  // ...
  // + 조사 교정 (받침 유무에 따른 이/가, 을/를, 은/는)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: CORE CODE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CORE CODE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 코드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT 응답 스키마 (Structured Outputs)&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;{
  &quot;episodeText&quot;: &quot;150-250 한글 음절&quot;,
  &quot;episodeSummary&quot;: &quot;40-60 한글 음절&quot;,
  &quot;location&quot;: &quot;string&quot;,
  &quot;currentDate&quot;: &quot;yyyy-MM-dd&quot;,
  &quot;currentTime&quot;: &quot;HH:MM&quot;,
  &quot;dayOfWeek&quot;: &quot;Monday|...|Sunday&quot;,
  &quot;subCharacterId&quot;: &quot;han_director|lee_associate|...|none&quot;,
  &quot;subCharacterEmotionType&quot;: &quot;happy|annoyed|anxious|tired|calm&quot;,
  &quot;mainCharacterEmotionType&quot;: &quot;happy|annoyed|anxious|tired|calm&quot;,
  &quot;hpDamageAmount&quot;: &quot;0-25&quot;,
  &quot;hpRecoveryAmount&quot;: &quot;0-25&quot;,
  &quot;stressDamageAmount&quot;: &quot;0-25&quot;,
  &quot;stressRecoveryAmount&quot;: &quot;0-25&quot;,
  &quot;fatigueDamageAmount&quot;: &quot;0-25&quot;,
  &quot;fatigueRecoveryAmount&quot;: &quot;0-25&quot;,
  &quot;nextActions&quot;: [
    { &quot;id&quot;: 1, &quot;text&quot;: &quot;선택지 (10-30 음절)&quot; },
    { &quot;id&quot;: 2, &quot;text&quot;: &quot;...&quot; },
    { &quot;id&quot;: 3, &quot;text&quot;: &quot;...&quot; }
  ],
  &quot;deathMessage&quot;: &quot;string|null&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;3스탯 + 3사망 시스템&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// turn-processor.service.ts
private checkDeathCondition(stats: Stats): DeathResult | null {
  if (stats.hp &amp;lt;= 0) {
    return {
      type: 'hp_death',
      message: '결국 쓰러졌다. 누군가 119를 불렀다.'
    };
  }
  if (stats.stress &amp;gt;= 100) {
    return {
      type: 'stress_death',
      message: '더는 무리였다. 사직서를 던졌다.'
    };
  }
  if (stats.fatigue &amp;gt;= 100) {
    return {
      type: 'fatigue_death',
      message: '책상이 마지막 침대가 되었다.'
    };
  }
  return null;
}

// 스탯 계산
const hpChange = hpRecovery - hpDamage;
const stressChange = stressDamage - stressRecovery;
const fatigueChange = fatigueDamage - fatigueRecovery;
const newHp = Math.max(0, Math.min(100, currentHp + hpChange));&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;CDK 스택 정의&lt;/p&gt;
&lt;pre class=&quot;scala&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// cdk/lib/kwonbackgame-stack.ts
export class KwonbackgameStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // DynamoDB Single Table
    const table = new dynamodb.Table(this, 'GameTable', {
      partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    // GSI 추가
    table.addGlobalSecondaryIndex({
      indexName: 'GSI1',
      partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING },
    });

    // Lambda 함수 (Game)
    const gameFunction = new lambda.Function(this, 'GameFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('dist/lambda/game'),
      memorySize: 512,
      timeout: cdk.Duration.seconds(30),
      environment: { TABLE_NAME: table.tableName },
      layers: [commonLayer],
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 6: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능 요약&lt;/p&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;7명 캐릭터 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;메인(권대리) + 6명 서브캐릭터(한 이사/이 대리/정 과장/오 부장/임 선임/강 전무). 각 캐릭터별 성격/아키타입 정의, 감정 기반 이미지 태그 시스템 (5감정 x 7캐릭터).&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;포인트 이코노미&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;가입 보너스 200pt, 출석 체크 100pt 획득. 턴 진행 시 10pt 소비. 게임 플레이를 위한 재화 시스템.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;시공간 연속성&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;날짜/시간/요일이 자연스럽게 흐르고, 미완료 업무(pendingTask)와 마감일(dueDate)을 추적하여 스토리에 반영.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 12px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px;&quot;&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #212529; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;서브캐릭터 다양성 추적&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96;&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터별 누적 등장 횟수를 추적하여 특정 캐릭터의 과다 등장을 방지하고 다양한 상호작용 유도.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://docs.aws.amazon.com/cdk/v2/guide/home.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS CDK Guide&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://platform.openai.com/docs/guides/structured-outputs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OpenAI Structured Outputs&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://www.alexdebrie.com/posts/dynamodb-single-table/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DynamoDB Single Table Design&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;AWS CDK를 활용해 Lambda + DynamoDB + API Gateway로 구성된 서버리스 게임 백엔드를 구축했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;3단계 계층적 메모리 시스템&lt;/b&gt;을 통해 GPT 토큰 한계 내에서도 30턴 이상의 장기 게임에서 내러티브 일관성을 유지할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DynamoDB Single Table Design&lt;/b&gt;으로 6가지 엔티티를 하나의 테이블에서 효율적으로 관리하고, &lt;b&gt;ResponseSafeParser&lt;/b&gt;로 AI 응답의 안정성을 99% 이상으로 확보했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;AI 기반 게임에서 가장 어려운 점은 &quot;일관성 있는 스토리 생성&quot;과 &quot;예측 불가능한 AI 응답 처리&quot;였는데, 이 두 가지 문제를 시스템 설계로 해결한 경험이 가장 값진 배움이었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 스토어 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;힘내라 권대리 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/qo9Dz/dJMcahwwfor/jLYRRTrvJgCmIO6qxHVM2k/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/8yPPU/dJMcaajQHFY/bppc2HAIbG68mkYd9sGB91/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/qjkeK/dJMb996jSkG/zZdPwJdzIIG9k9VQMiyEKK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/t2Le6/dJMcaa5dDjk/okEsaxGRzb0qiU3jtqCIL0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cfs7lC/dJMcaa5dDjm/eikx8XoI9KQIUQcwRFTgS0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/byXoRf/dJMcahwwfot/kcV1kxG6eYEtv652fmlqY0/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qo9Dz/dJMcahwwfor/jLYRRTrvJgCmIO6qxHVM2k/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/qo9Dz/dJMcahwwfor/jLYRRTrvJgCmIO6qxHVM2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qo9Dz/dJMcahwwfor/jLYRRTrvJgCmIO6qxHVM2k/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqo9Dz%2FdJMcahwwfor%2FjLYRRTrvJgCmIO6qxHVM2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8yPPU/dJMcaajQHFY/bppc2HAIbG68mkYd9sGB91/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/8yPPU/dJMcaajQHFY/bppc2HAIbG68mkYd9sGB91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8yPPU/dJMcaajQHFY/bppc2HAIbG68mkYd9sGB91/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8yPPU%2FdJMcaajQHFY%2Fbppc2HAIbG68mkYd9sGB91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjkeK/dJMb996jSkG/zZdPwJdzIIG9k9VQMiyEKK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/qjkeK/dJMb996jSkG/zZdPwJdzIIG9k9VQMiyEKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjkeK/dJMb996jSkG/zZdPwJdzIIG9k9VQMiyEKK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjkeK%2FdJMb996jSkG%2FzZdPwJdzIIG9k9VQMiyEKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t2Le6/dJMcaa5dDjk/okEsaxGRzb0qiU3jtqCIL0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/t2Le6/dJMcaa5dDjk/okEsaxGRzb0qiU3jtqCIL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t2Le6/dJMcaa5dDjk/okEsaxGRzb0qiU3jtqCIL0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft2Le6%2FdJMcaa5dDjk%2FokEsaxGRzb0qiU3jtqCIL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfs7lC/dJMcaa5dDjm/eikx8XoI9KQIUQcwRFTgS0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cfs7lC/dJMcaa5dDjm/eikx8XoI9KQIUQcwRFTgS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfs7lC/dJMcaa5dDjm/eikx8XoI9KQIUQcwRFTgS0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfs7lC%2FdJMcaa5dDjm%2Feikx8XoI9KQIUQcwRFTgS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byXoRf/dJMcahwwfot/kcV1kxG6eYEtv652fmlqY0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/byXoRf/dJMcahwwfot/kcV1kxG6eYEtv652fmlqY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byXoRf/dJMcahwwfot/kcV1kxG6eYEtv652fmlqY0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyXoRf%2FdJMcahwwfot%2FkcV1kxG6eYEtv652fmlqY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonbackgame.kwondaeri&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6753064792&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트를 진행하면서 가장 고민했던 부분은 &quot;GPT가 이전 스토리를 기억하게 하는 방법&quot;이었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 모든 히스토리를 넣으면 토큰 한계에 걸리고, 너무 적게 넣으면 맥락이 끊어집니다. 이 균형점을 찾기 위해 3단계 메모리 시스템을 설계했고, 실제 30턴 이상 플레이해도 캐릭터 관계와 사건의 연속성이 유지되는 것을 확인했을 때 큰 보람을 느꼈습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;또한 DynamoDB Single Table Design을 실제 프로젝트에 적용해보면서 NoSQL의 사고방식을 체득할 수 있었습니다. &quot;어떤 쿼리 패턴이 필요한가?&quot;를 먼저 정의하고 그에 맞춰 키를 설계하는 접근법은 RDB와 완전히 다른 경험이었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;이 글이 서버리스 아키텍처나 AI 기반 게임 개발에 관심 있으신 분들께 도움이 되었으면 합니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/백엔드</category>
      <category>ai 게임 개발</category>
      <category>AWS CDK</category>
      <category>aws lambda</category>
      <category>dynamodb</category>
      <category>OpenAI GPT</category>
      <category>Prompt Engineering</category>
      <category>Single Table Design</category>
      <category>TypeScript</category>
      <category>내러티브 게임</category>
      <category>서버리스 아키텍처</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/591</guid>
      <comments>https://kwonputer.tistory.com/591#entry591comment</comments>
      <pubDate>Mon, 2 Feb 2026 15:11:46 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AI 턴제 내러티브 게임 '힘내라 권대리' 개발기 - Clean Architecture + MVVM + Riverpod</title>
      <link>https://kwonputer.tistory.com/590</link>
      <description>&lt;!-- 스토어 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;힘내라 권대리 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cyMNpb/dJMcafZKq4n/03NCgJDfFAuIRN4O37GxH0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/FycNa/dJMcahXAqco/4ERKGbsPtlmAZo6rkkdBQk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/RIhw1/dJMcaac5BqP/5zd55rYxwwW4ITXA3zrgu0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/I9ORa/dJMb99Zzyix/sBzmGlc3weYTkoXIgiWzV1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/yBGqw/dJMcaac5BqQ/06BJracTkB4DH8y8WmII4K/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dsdYLM/dJMb99Zzyiy/feHsmF6SHigEIEOkTQ2wtk/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyMNpb/dJMcafZKq4n/03NCgJDfFAuIRN4O37GxH0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cyMNpb/dJMcafZKq4n/03NCgJDfFAuIRN4O37GxH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyMNpb/dJMcafZKq4n/03NCgJDfFAuIRN4O37GxH0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyMNpb%2FdJMcafZKq4n%2F03NCgJDfFAuIRN4O37GxH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FycNa/dJMcahXAqco/4ERKGbsPtlmAZo6rkkdBQk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/FycNa/dJMcahXAqco/4ERKGbsPtlmAZo6rkkdBQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FycNa/dJMcahXAqco/4ERKGbsPtlmAZo6rkkdBQk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFycNa%2FdJMcahXAqco%2F4ERKGbsPtlmAZo6rkkdBQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RIhw1/dJMcaac5BqP/5zd55rYxwwW4ITXA3zrgu0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/RIhw1/dJMcaac5BqP/5zd55rYxwwW4ITXA3zrgu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RIhw1/dJMcaac5BqP/5zd55rYxwwW4ITXA3zrgu0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRIhw1%2FdJMcaac5BqP%2F5zd55rYxwwW4ITXA3zrgu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I9ORa/dJMb99Zzyix/sBzmGlc3weYTkoXIgiWzV1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/I9ORa/dJMb99Zzyix/sBzmGlc3weYTkoXIgiWzV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I9ORa/dJMb99Zzyix/sBzmGlc3weYTkoXIgiWzV1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI9ORa%2FdJMb99Zzyix%2FsBzmGlc3weYTkoXIgiWzV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yBGqw/dJMcaac5BqQ/06BJracTkB4DH8y8WmII4K/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/yBGqw/dJMcaac5BqQ/06BJracTkB4DH8y8WmII4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yBGqw/dJMcaac5BqQ/06BJracTkB4DH8y8WmII4K/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyBGqw%2FdJMcaac5BqQ%2F06BJracTkB4DH8y8WmII4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsdYLM/dJMb99Zzyiy/feHsmF6SHigEIEOkTQ2wtk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dsdYLM/dJMb99Zzyiy/feHsmF6SHigEIEOkTQ2wtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsdYLM/dJMb99Zzyiy/feHsmF6SHigEIEOkTQ2wtk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsdYLM%2FdJMb99Zzyiy%2FfeHsmF6SHigEIEOkTQ2wtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonbackgame.kwondaeri&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6753064792&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;힘내라 권대리 - Flutter AI 턴제 내러티브 게임&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;한국 직장인의 삶을 시뮬레이션하는 AI 기반 턴제 내러티브 게임 모바일 앱 개발기입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3개 스탯&lt;/b&gt;(체력/정신/체면)과 &lt;b&gt;3가지 사망 엔딩&lt;/b&gt;(과로사/번아웃/실직)을 갖춘,&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPT-4o-mini&lt;/b&gt; 기반 매 턴 에피소드와 선택지를 생성하는 게임입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;AI 응답 대기 UX 최적화&lt;/b&gt;, &lt;b&gt;오디오 라이프사이클 동기화&lt;/b&gt;, &lt;b&gt;Clean Architecture + MVVM + Riverpod&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 이 앱을 만들었나?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능 소개&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;아키텍처&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;결론&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: WHY --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이 앱을 만들었나?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;오늘도 야근이다, 권대리&quot;&lt;/b&gt; - 한국 직장인이라면 누구나 공감할 수 있는 이야기를 게임으로 만들고 싶었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT의 창작 능력을 활용하면 매번 다른 스토리가 펼쳐지는 &lt;b&gt;무한 리플레이&lt;/b&gt; 게임을 만들 수 있겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 텍스트를 생성하는 것을 넘어서, &lt;b&gt;캐릭터 감정&lt;/b&gt;, &lt;b&gt;스탯 변화&lt;/b&gt;, &lt;b&gt;사망 엔딩&lt;/b&gt;까지 게임적 요소를 갖춘 완성도 높은 앱을 목표로 했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter로 Android/iOS 동시 지원, Clean Architecture로 유지보수성 확보, Riverpod으로 반응형 상태관리를 구현했습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능 소개&lt;/p&gt;
&lt;!-- AI 턴제 내러티브 게임 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 턴제 내러티브 게임 UI&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에피소드 표시&lt;/b&gt; &amp;mdash; GPT가 생성한 150-250자 분량의 에피소드를 flutter_html로 렌더링합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택지 인터랙션&lt;/b&gt; &amp;mdash; 2-3개의 선택지 중 하나를 선택하면 다음 턴이 진행됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스탯 바 애니메이션&lt;/b&gt; &amp;mdash; 체력/정신/체면 3개 스탯이 0-100 범위에서 실시간으로 시각화됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 3개 스탯 + 3가지 사망 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #e03131; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;3개 스탯 + 3가지 사망 엔딩&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;체력(HP) &amp;lt;= 0&lt;/b&gt; &amp;mdash; 과로사: &quot;결국 쓰러졌다. 누군가 119를 불렀다.&quot;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정신(Stress) &amp;gt;= 100&lt;/b&gt; &amp;mdash; 번아웃: &quot;더는 무리였다. 사직서를 던졌다.&quot;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;체면(Fatigue) &amp;gt;= 100&lt;/b&gt; &amp;mdash; 실직: &quot;책상이 마지막 침대가 되었다.&quot;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 오디오 시스템 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;AudioManager BGM/SFX 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;just_audio 기반&lt;/b&gt; &amp;mdash; 배경음악(BGM)과 효과음(SFX)을 싱글톤 매니저로 전역 관리합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라이프사이클 연동&lt;/b&gt; &amp;mdash; 앱 백그라운드 전환 시 자동 일시정지, 포그라운드 복귀 시 자동 재개됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설정 영구 저장&lt;/b&gt; &amp;mdash; 사운드 ON/OFF 설정이 UserSettingsDatabase(Hive)에 저장됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 7명 캐릭터 시스템 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;7명 캐릭터 + 감정 이미지 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;메인 캐릭터 &lt;b&gt;권대리&lt;/b&gt;와 6명의 서브 캐릭터가 각각 5가지 감정별 이미지를 가집니다.&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;권대리&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;강 전무&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;한 이사&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;정 과장&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;오 부장&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;임 선임&lt;/code&gt; &lt;code style=&quot;background-color: #f1f3f5; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057;&quot;&gt;이 대리&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 로그인 + 악용 방지 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;3중 로그인 + AbusePrevention&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kakao/Apple/Guest&lt;/b&gt; &amp;mdash; 3가지 로그인 방식을 지원하여 사용자 접근성을 높였습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TokenManagerService&lt;/b&gt; &amp;mdash; 24시간 전 자동 갱신, 30분 주기 검증으로 인증 상태를 관리합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AbusePrevention DB&lt;/b&gt; &amp;mdash; 가입 보너스/출석 기록을 영구 저장하여 로그아웃/탈퇴 후 재가입 시 악용을 방지합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 응답 대기 시간과 사용자 경험&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT API 응답 시간이 &lt;b&gt;5-30초&lt;/b&gt;로 불확실하고, Lambda Cold Start가 결합되면 최대 35초까지 대기가 발생했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;단순 로딩 스피너만으로는 사용자가 앱이 멈춘 것으로 오인하고 이탈하는 문제가 있었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; 로딩을 게임 경험의 일부로 전환&lt;/p&gt;
&lt;pre class=&quot;lsl&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// EpisodeLoadingWidget 구성 요소
1. AnimatedTextKit - 타이핑 애니메이션으로 &quot;권대리의 하루가 시작됩니다...&quot;
2. Lottie - 캐릭터 애니메이션으로 시선 유도
3. 진행 힌트 메시지 - &quot;AI가 다음 에피소드를 생성 중입니다&quot;
4. Shimmer - 결과 영역 미리 표시로 기대감 형성
5. BGM 유지 - AudioManager로 로딩 중에도 음악 재생&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;오디오 라이프사이클과 상태 동기화&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;BGM이 앱 백그라운드 전환 시 계속 재생되거나, 페이지 전환 시 중복 재생되거나, 사운드 설정이 즉시 반영되지 않는 문제가 있었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; AudioManager 싱글톤 + 라이프사이클 연동&lt;/p&gt;
&lt;pre class=&quot;scala&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// main.dart에서 라이프사이클 감지
class _MyAppState extends State&amp;lt;MyApp&amp;gt; with WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.paused:
        AudioManager().pauseBGM();  // 백그라운드 -&amp;gt; 일시정지
        break;
      case AppLifecycleState.resumed:
        AudioManager().resumeBGM(); // 포그라운드 -&amp;gt; 재개
        break;
    }
  }
}

// UserSettingsDatabase(Hive)에 사운드 설정 영구 저장
// SoundButton 위젯으로 설정 변경 즉시 반영&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Hive DB 초기화 순서와 의존성 관리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;5개의 Hive DB(UserSettings/AbusePrevention/Cache/AnnouncementRead/Memorial)를 사용하면서, 초기화 순서와 DI 컨테이너 등록 순서가 꼬이는 문제가 발생했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION &amp;mdash; Injectable @preResolve + 명시적 초기화 순서&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// main.dart 초기화 순서
Future&amp;lt;void&amp;gt; main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. Hive 초기화 (가장 먼저)
  await Hive.initFlutter();

  // 2. 각 DB Box 열기
  await Future.wait([
    Hive.openBox('userSettings'),
    Hive.openBox('abusePrevention'),  // 절대 삭제 안 됨
    Hive.openBox('cache'),
    Hive.openBox('announcementRead'),
    Hive.openBox('memorial'),
  ]);

  // 3. DI 컨테이너 초기화
  await configureDependencies();

  // 4. Firebase 초기화
  await Firebase.initializeApp();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: ARCHITECTURE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;아키텍처&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Clean Architecture + MVVM + Riverpod&lt;/p&gt;
&lt;pre class=&quot;crystal&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;lib/
├── presenter/           # Presentation Layer
│   ├── pages/          # 12개 UI 화면 (Login/Game/MyCharacters/Settings)
│   ├── viewmodels/     # Riverpod 기반 ViewModel (BaseViewModel 상속)
│   ├── widgets/        # 재사용 위젯 (common/dialog/card)
│   └── styles/         # KColors, Themes, TextStyles
│
├── domain/              # Domain Layer
│   ├── models/         # users/characters/games/announcements 모델
│   ├── repositories/   # Repository 인터페이스
│   └── usecases/       # 32개+ UseCase (user/character/game/point)
│
├── data/                # Data Layer
│   ├── repositories/   # Repository 구현체
│   ├── services/       # ApiService/TokenManagerService
│   ├── dbs/           # Hive 5개 DB
│   └── configs/        # ApiConfig (Dio 인터셉터)
│
└── di/                  # GetIt + Injectable DI&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술 스택&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Framework&lt;/b&gt; &amp;mdash; Flutter 3.32.1, Dart SDK ^3.5.4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태관리&lt;/b&gt; &amp;mdash; flutter_riverpod 2.6.1 + riverpod_annotation 2.6.1&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라우팅&lt;/b&gt; &amp;mdash; GoRouter 15.1.2 + go_transitions 0.8.1&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI&lt;/b&gt; &amp;mdash; GetIt 8.0.3 + Injectable 2.5.0&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬 DB&lt;/b&gt; &amp;mdash; Hive 2.2.3&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네트워킹&lt;/b&gt; &amp;mdash; Dio 5.8.0&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오디오&lt;/b&gt; &amp;mdash; just_audio 0.10.4&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;애니메이션&lt;/b&gt; &amp;mdash; flutter_animate 4.5.2, Lottie 3.2.0, animated_text_kit 4.2.3&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;TECH STACK&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://riverpod.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Riverpod&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/go_router&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GoRouter&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/hive&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hive&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/just_audio&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;just_audio&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힘내라 권대리&lt;/b&gt;는 GPT의 창작 능력과 Flutter의 크로스플랫폼 장점을 결합한 AI 턴제 내러티브 게임입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Clean Architecture + MVVM + Riverpod 아키텍처로 앱을 관리했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;AI 응답 대기 시간을 게임 경험으로 전환&lt;/b&gt;하고, &lt;b&gt;오디오 라이프사이클을 완벽히 동기화&lt;/b&gt;하는 데 집중했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드는 AWS Serverless(Lambda + DynamoDB + GPT-4o-mini)로 구성되어 있으며, 별도 포스트에서 다룰 예정입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 스토어 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;힘내라 권대리 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ch0UZJ/dJMcaiB9v3v/T6gX9ipql3xqcf8UVHoEt0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ea7JLt/dJMcaiB9v3w/wHd1IcZbpYBkzfMB8Yzu4K/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/brsjA2/dJMcafFswhT/fsK85i5Pvjx1k0qZcvRtLk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dTDdLD/dJMcajnu1Rt/1Sihc2aavZUkKjXFlLJR0K/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bRs52Z/dJMcacPwbxc/CfJtUww4tQkqw0TkZBbgwk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/GIufJ/dJMcaiB9v3x/2EjDve6ykkkvAgVZeVI010/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ch0UZJ/dJMcaiB9v3v/T6gX9ipql3xqcf8UVHoEt0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ch0UZJ/dJMcaiB9v3v/T6gX9ipql3xqcf8UVHoEt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ch0UZJ/dJMcaiB9v3v/T6gX9ipql3xqcf8UVHoEt0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fch0UZJ%2FdJMcaiB9v3v%2FT6gX9ipql3xqcf8UVHoEt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ea7JLt/dJMcaiB9v3w/wHd1IcZbpYBkzfMB8Yzu4K/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ea7JLt/dJMcaiB9v3w/wHd1IcZbpYBkzfMB8Yzu4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ea7JLt/dJMcaiB9v3w/wHd1IcZbpYBkzfMB8Yzu4K/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fea7JLt%2FdJMcaiB9v3w%2FwHd1IcZbpYBkzfMB8Yzu4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brsjA2/dJMcafFswhT/fsK85i5Pvjx1k0qZcvRtLk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/brsjA2/dJMcafFswhT/fsK85i5Pvjx1k0qZcvRtLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brsjA2/dJMcafFswhT/fsK85i5Pvjx1k0qZcvRtLk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrsjA2%2FdJMcafFswhT%2FfsK85i5Pvjx1k0qZcvRtLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTDdLD/dJMcajnu1Rt/1Sihc2aavZUkKjXFlLJR0K/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dTDdLD/dJMcajnu1Rt/1Sihc2aavZUkKjXFlLJR0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTDdLD/dJMcajnu1Rt/1Sihc2aavZUkKjXFlLJR0K/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTDdLD%2FdJMcajnu1Rt%2F1Sihc2aavZUkKjXFlLJR0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRs52Z/dJMcacPwbxc/CfJtUww4tQkqw0TkZBbgwk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bRs52Z/dJMcacPwbxc/CfJtUww4tQkqw0TkZBbgwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRs52Z/dJMcacPwbxc/CfJtUww4tQkqw0TkZBbgwk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRs52Z%2FdJMcacPwbxc%2FCfJtUww4tQkqw0TkZBbgwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIufJ/dJMcaiB9v3x/2EjDve6ykkkvAgVZeVI010/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/GIufJ/dJMcaiB9v3x/2EjDve6ykkkvAgVZeVI010/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIufJ/dJMcaiB9v3x/2EjDve6ykkkvAgVZeVI010/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIufJ%2FdJMcaiB9v3x%2F2EjDve6ykkkvAgVZeVI010%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2778&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonbackgame.kwondaeri&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6753064792&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;오늘도 야근이다, 권대리&quot; - 이 한 문장에서 시작된 프로젝트가 어느새 서비스가 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT가 생성한 에피소드를 처음 읽었을 때, 권대리의 고단한 하루에 정말 공감이 되어서 웃음이 났던 기억이 납니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;AI와 게임의 결합, Clean Architecture의 실전 적용, 오디오 시스템 설계 등 많은 것을 배울 수 있는 프로젝트였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 &lt;b&gt;백엔드 아키텍처(AWS Lambda + DynamoDB + GPT)&lt;/b&gt;를 다룰 예정입니다. 감사합니다!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/프로젝트</category>
      <category>ai 게임 개발</category>
      <category>Clean Architecture</category>
      <category>flutter</category>
      <category>gpt 연동</category>
      <category>Hive</category>
      <category>just_audio</category>
      <category>riverpod</category>
      <category>게임 ux</category>
      <category>내러티브 게임</category>
      <category>상태관리</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/590</guid>
      <comments>https://kwonputer.tistory.com/590#entry590comment</comments>
      <pubDate>Mon, 2 Feb 2026 15:09:40 +0900</pubDate>
    </item>
    <item>
      <title>[Backend] AI 핑계 생성 서비스 서버리스 백엔드 - CDK + Lambda + DynamoDB GSI</title>
      <link>https://kwonputer.tistory.com/589</link>
      <description>&lt;!-- 스토어 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/pCOGX/dJMcaiIUogz/vBjn2Cz3kE69k6G2scD6Mk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/wzBGe/dJMcai3cvgg/mKLVEHufqKrYE7fKvBK0Q0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cAJRO2/dJMcaiIUogD/kLCYh6NZIRe6smjZ04OERk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/Ygukg/dJMcai3cvgf/yBFETgYhksSfXlcIoeMAik/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/b7voa0/dJMcaiIUogC/vTOW3FkDWkxjkcCAwsskp0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/n5ozi/dJMcaiIUogA/9oUKdplwCSL98UD9kERLfK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bxlqRj/dJMcaiIUogB/xxj5B56PPe7kp2XYyw9hCK/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pCOGX/dJMcaiIUogz/vBjn2Cz3kE69k6G2scD6Mk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/pCOGX/dJMcaiIUogz/vBjn2Cz3kE69k6G2scD6Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pCOGX/dJMcaiIUogz/vBjn2Cz3kE69k6G2scD6Mk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpCOGX%2FdJMcaiIUogz%2FvBjn2Cz3kE69k6G2scD6Mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wzBGe/dJMcai3cvgg/mKLVEHufqKrYE7fKvBK0Q0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/wzBGe/dJMcai3cvgg/mKLVEHufqKrYE7fKvBK0Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wzBGe/dJMcai3cvgg/mKLVEHufqKrYE7fKvBK0Q0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwzBGe%2FdJMcai3cvgg%2FmKLVEHufqKrYE7fKvBK0Q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAJRO2/dJMcaiIUogD/kLCYh6NZIRe6smjZ04OERk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cAJRO2/dJMcaiIUogD/kLCYh6NZIRe6smjZ04OERk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAJRO2/dJMcaiIUogD/kLCYh6NZIRe6smjZ04OERk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAJRO2%2FdJMcaiIUogD%2FkLCYh6NZIRe6smjZ04OERk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ygukg/dJMcai3cvgf/yBFETgYhksSfXlcIoeMAik/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/Ygukg/dJMcai3cvgf/yBFETgYhksSfXlcIoeMAik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ygukg/dJMcai3cvgf/yBFETgYhksSfXlcIoeMAik/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYgukg%2FdJMcai3cvgf%2FyBFETgYhksSfXlcIoeMAik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7voa0/dJMcaiIUogC/vTOW3FkDWkxjkcCAwsskp0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/b7voa0/dJMcaiIUogC/vTOW3FkDWkxjkcCAwsskp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7voa0/dJMcaiIUogC/vTOW3FkDWkxjkcCAwsskp0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7voa0%2FdJMcaiIUogC%2FvTOW3FkDWkxjkcCAwsskp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n5ozi/dJMcaiIUogA/9oUKdplwCSL98UD9kERLfK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/n5ozi/dJMcaiIUogA/9oUKdplwCSL98UD9kERLfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n5ozi/dJMcaiIUogA/9oUKdplwCSL98UD9kERLfK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn5ozi%2FdJMcaiIUogA%2F9oUKdplwCSL98UD9kERLfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxlqRj/dJMcaiIUogB/xxj5B56PPe7kp2XYyw9hCK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bxlqRj/dJMcaiIUogB/xxj5B56PPe7kp2XYyw9hCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxlqRj/dJMcaiIUogB/xxj5B56PPe7kp2XYyw9hCK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxlqRj%2FdJMcaiIUogB%2Fxxj5B56PPe7kp2XYyw9hCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonback.kxcuse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6751330034&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야 백엔드 - AWS Serverless 아키텍처&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 핑계/변명 생성 서비스의 서버리스 백엔드 개발기입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS CDK&lt;/b&gt;로 인프라를 코드로 관리하고, &lt;b&gt;Lambda 함수&lt;/b&gt;가 각각의 도메인을 처리합니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DynamoDB Single Table Design GSI&lt;/b&gt;로 엔티티의 다양한 접근 패턴을 지원합니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;Single Table Design GSI 설계&lt;/b&gt;, &lt;b&gt;Lambda Cold Start 최적화&lt;/b&gt;, &lt;b&gt;AI 파이프라인 구축&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 서버리스를 선택했나?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;AWS 아키텍처&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;AI 파이프라인&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: WHY --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 서버리스를 선택했나?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;뻥이야&lt;/b&gt;는 AI 기반 핑계/변명 생성 서비스입니다. OpenAI GPT API를 활용하여 상황에 맞는 창의적인 핑계를 생성하고, 캐릭터 페르소나와 말투를 적용해 개성 있는 결과물을 만들어냅니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;이 서비스의 특성상 &lt;b&gt;트래픽 변동이 크고&lt;/b&gt;, &lt;b&gt;API 호출 당 비용이 발생&lt;/b&gt;하며, &lt;b&gt;인프라 관리에 리소스를 쓰고 싶지 않았습니다&lt;/b&gt;. 서버리스 아키텍처는 이 세 가지 요구사항을 완벽히 충족했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;AWS CDK&lt;/b&gt;를 선택한 이유는 인프라를 TypeScript 코드로 관리할 수 있어, 백엔드 코드와 동일한 언어로 전체 스택을 유지할 수 있기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 &lt;b&gt;Lambda&lt;/b&gt;, &lt;b&gt;API Gateway&lt;/b&gt;, &lt;b&gt;DynamoDB Single Table&lt;/b&gt;, &lt;b&gt;EventBridge 스케줄러&lt;/b&gt;로 구성된 완전한 서버리스 아키텍처를 구축했습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: ARCHITECTURE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;AWS 아키텍처&lt;/p&gt;
&lt;!-- AWS 아키텍처 다이어그램 --&gt;
&lt;div style=&quot;background-color: #1e1e1e; border-radius: 12px; padding: 20px;&quot;&gt;
&lt;pre class=&quot;gherkin&quot; style=&quot;color: #d4d4d4; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;                        [Flutter App]
                            |
                            v
                    [API Gateway REST]
                    rate: 20/s, burst: 40
                    stage: dev
                            |
          +---------+-------+-------+---------+---------+
          |         |       |       |         |         |
       [excuse]  [user] [character] [tone] [announce] [popularity]
       512MB     512MB    512MB    256MB    256MB      512MB
       30s       45s      30s      30s      30s        120s
          |         |       |       |         |         |
          +----+----+---+---+---+---+---------+         |
               |        |                               |
               v        v                               |
         [DynamoDB]  [Secrets Manager]                  |
         Single Table  OpenAI API Key                   |
         PK/SK + 6 GSI Kakao Admin Key                  |
         TTL enabled                                    |
               ^                                        |
               |                                        |
               +----------------------------------------+
                                |
                       [EventBridge]
                       cron(0 0 * * ? *)
                       매일 자정 인기도 캐시 갱신&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- Lambda 함수 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;6개 Lambda 함수&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;excuse&lt;/b&gt;&amp;nbsp;- 핑계 생성/목록/좋아요/운세/썰뻥튀기&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;user&lt;/b&gt;&amp;nbsp;- Kakao/Apple OAuth 인증, 프로필, 포인트, 출석&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;character&lt;/b&gt;&amp;nbsp;- 캐릭터 CRUD, AI 성격 생성, 커스텀 핑계&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;tone&lt;/b&gt;&amp;nbsp;- 말투 CRUD&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;announcement&lt;/b&gt;&amp;nbsp;- 공지사항, 앱 상태, 강제 업데이트&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;popularity-updater&lt;/b&gt;&amp;nbsp;- EventBridge 트리거, 인기도 캐시 갱신&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- DynamoDB 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;DynamoDB Single Table Design&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔티티&lt;/b&gt; - USER# / EXCUSE# / CHAR# / TONE# / POINT# / ANNOUNCEMENT# / POPULAR_CACHE&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI1&lt;/b&gt; - 사용자별 조회&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI2&lt;/b&gt; - 글로벌 뻥 조회 (10개 샤드)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI3&lt;/b&gt; - 인기 뻥 조회 (10개 샤드)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI4&lt;/b&gt; - 시간대별 인기 캐시&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI5&lt;/b&gt; - 사용자별 뻥 전용 인덱스&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GSI6&lt;/b&gt; - 캐릭터별 필터링&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: AI PIPELINE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;AI PIPELINE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;AI 파이프라인&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야는 &lt;b&gt;3가지 독립적인 AI 생성 파이프라인&lt;/b&gt;을 가지고 있습니다. 모두 OpenAIService를 공유하며, 각각 다른 목적의 콘텐츠를 생성합니다.&lt;/p&gt;
&lt;!-- AI 파이프라인 다이어그램 --&gt;
&lt;div style=&quot;background-color: #1e1e1e; border-radius: 12px; padding: 20px;&quot;&gt;
&lt;pre class=&quot;gherkin&quot; style=&quot;color: #d4d4d4; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;[사용자]
   |
   | situation + characterId? + toneId?
   v
[API Gateway] ─── POST /excuses ──&amp;gt; [excuse Lambda]
                                         |
                          +--------------+---------------+
                          |                              |
                   1. CategoryClassifier          2. CreativeTransformer
                   GPT-4o-mini (분류)             GPT-4.1-mini (생성)
                   |                              |
                   | type: excuse/reason           | 기본 모드 (temp 0.8)
                   | category: 16개               | 캐릭터 모드 (temp 0.7)
                   | shouldTransform              | + Personality 4축
                   v                              | + Tone 말투
                                                  v
                                          [구조화 JSON 응답]
                                          content + emotion + tags
                                                  |
                                                  v
                                          [DynamoDB 저장]
                                          + 포인트 차감(-10pt)
                                          + 감정 이미지 매핑
                                                  |
                                                  v
                                             [사용자 응답]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 파이프라인 A: 핑계 생성 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;파이프라인 A: 핑계/이유 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계 - CategoryClassifier&lt;/b&gt;: GPT-4o-mini로 입력 분석 (16개 카테고리 분류, excuse/reason 타입 판별)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계 - CreativeTransformer&lt;/b&gt;: GPT-4.1-mini로 창의적 핑계 생성 (캐릭터 성격 4축 + Tone 말투 적용)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트 차감&lt;/b&gt;: -10pt (실패 시 자동 환불)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 파이프라인 B: 운세 생성 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;파이프라인 B: 오늘의 운세 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사전 계산&lt;/b&gt;: 육십갑자, 타로카드(22장), 별자리(12개), 12지신&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPT-4.1-mini&lt;/b&gt;: 한국/서양 점술 융합 운세 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트 차감&lt;/b&gt;: -40pt&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 파이프라인 C: 썰 뻥튀기 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;파이프라인 C: 썰 뻥튀기기&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장르 선택&lt;/b&gt;: 10개 웹소설 장르 중 자동 선택 (게임판타지, 회귀, 빙의, 환생, 로판 등)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPT-4.1-mini&lt;/b&gt;: 오프닝 &amp;rarr; 캐릭터 상태 &amp;rarr; 갈등/전환점 &amp;rarr; 행동/반응 &amp;rarr; 클리프행어 구조&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트 차감&lt;/b&gt;: -30pt&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능&lt;/p&gt;
&lt;!-- 캐릭터 + 말투 시스템 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터 페르소나 + 말투(Tone) 커스터마이징&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐릭터 성격 4축&lt;/b&gt;: intensityLevel(긴장/강도), chaosLevel(논리적 무작위성), formalityLevel(한국어 격식), imaginationLevel(현실초월)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 자동 분석&lt;/b&gt;: 캐릭터 이름과 설명만 입력하면 GPT-4o-mini가 4축 성격 수치를 자동 생성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tone 적용&lt;/b&gt;: 사용자 정의 말투를 핑계 생성 프롬프트에 주입하여 문체 변환&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 포인트 이코노미 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;포인트 이코노미 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;획득 (+)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가입 보너스&lt;/b&gt; +200pt | &lt;b&gt;출석체크&lt;/b&gt; +50pt | &lt;b&gt;좋아요&lt;/b&gt; +7pt&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #868e96; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;사용 (-)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핑계 생성&lt;/b&gt; -10pt | &lt;b&gt;운세 생성&lt;/b&gt; -40pt | &lt;b&gt;썰 뻥튀기&lt;/b&gt; -30pt&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 인기 랭킹 시스템 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;인기 랭킹 시스템 (EventBridge 스케줄러)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매일 자정 UTC&lt;/b&gt;: popularity-updater Lambda가 인기도 캐시 갱신&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3개 기간&lt;/b&gt;: daily(24시간), weekly(7일), monthly(30일) 병렬 처리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;샤드 분산 쿼리&lt;/b&gt;: GSI3로 10개 샤드 병렬 조회 &amp;rarr; 상위 100개 캐싱 (TTL 24시간)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1: DynamoDB Single Table Design --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;DynamoDB Single Table Design에서 다양한 접근 패턴 지원&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;엔티티(User/Excuse/Character/Tone/Point/Announcement/PopularityCache)가 &lt;b&gt;사용자별, 글로벌, 시간 기반, 캐릭터별&lt;/b&gt; 등 다양한 쿼리 패턴을 필요로 하면서 단일 테이블로 통합 관리해야 했습니다. DynamoDB는 관계형 DB와 달리 &lt;b&gt;설계 시점에 접근 패턴을 결정&lt;/b&gt;해야 하며, 잘못된 GSI 설계는 핫 파티션과 스로틀링을 유발합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - PK/SK 패턴 + GSI 설계&lt;/p&gt;
&lt;pre class=&quot;clean&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// PK/SK 패턴 (엔티티별 프리픽스)
PK: USER#{userId} / EXCUSE#{id} / CHAR#{id} / TONE#{id} / POINT#{userId} / ANNOUNCEMENT#{id} / POPULAR_CACHE

// 6개 GSI
GSI1: 사용자별 조회 (GSI1PK / GSI1SK)
GSI2: 글로벌 뻥 (ALL_EXCUSES#SHARD#{0-9}) - 10개 샤드로 핫 파티션 방지
GSI3: 인기 뻥 (POPULAR_EXCUSES#SHARD#{0-9}) - 샤드 분산 쿼리
GSI4: 시간대별 인기 캐시
GSI5: 사용자별 뻥 전용
GSI6: 캐릭터별 필터링

// TTL 자동 캐시 정리
ttl 속성 활성화 &amp;rarr; 24시간 후 자동 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2: Lambda Cold Start + GPT API 지연 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;Lambda Cold Start + OpenAI GPT API 응답 지연&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;OpenAI GPT API 호출이 &lt;b&gt;5-30초&lt;/b&gt; 소요되며, Lambda Cold Start(&lt;b&gt;2-5초&lt;/b&gt;)와 결합하면 최대 &lt;b&gt;35초 지연&lt;/b&gt;으로 사용자 경험이 저하되었습니다. Lambda 함수 초기화 시 AWS SDK, Secrets Manager 조회, 외부 라이브러리 로드에 Cold Start 시간이 추가되는 문제가 있었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - 다각도 최적화&lt;/p&gt;
&lt;div style=&quot;background-color: #f0fff4; border-radius: 6px; padding: 14px; margin-bottom: 10px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;Lambda Layer 분리&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;공통 의존성을 Lambda Layer로 분리 (layer-package.json) &amp;rarr; Cold Start 시간 감소&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f0fff4; border-radius: 6px; padding: 14px; margin-bottom: 10px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;Webpack 5 번들링&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;멀티 엔트리 번들링으로 코드 크기 최소화 &amp;rarr; Lambda 패키지 경량화&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f0fff4; border-radius: 6px; padding: 14px; margin-bottom: 10px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;메모리 차등 할당&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;256-512MB 함수별 차등 설정 &amp;rarr; CPU 성능 비례 향상&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f0fff4; border-radius: 6px; padding: 14px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;Secrets Manager 캐싱&lt;/p&gt;
&lt;p style=&quot;font-size: 13px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;최초 조회 후 메모리 캐싱 (this.apiKey) &amp;rarr; 런타임 중 재조회 최소화&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3: GPT 프롬프트 최적화 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;GPT 응답의 창의성 + 일관성 균형&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;핑계 생성 시 &lt;b&gt;창의적이면서도 포맷이 일관된&lt;/b&gt; 응답이 필요했습니다. Temperature만 조절하면 창의성은 올라가지만 포맷이 깨지고, 낮추면 반복적인 결과가 나오는 문제가 있었습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - Frequency/Presence Penalty 튜닝&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// openai-params.config.ts
// 설계 원칙 (코드 주석에서 확인)
// - Temperature는 창의성과 약한 상관관계만 있음
// - Frequency/Presence penalty가 창의적 다양성의 핵심
// - 한국어는 영어 대비 높은 penalty 값이 효과적
// - top_p 제거 (temperature와 중복 효과)

창의적 변형 (일반 핑계): temp=0.8, freq_penalty=1.0, pres_penalty=0.75
캐릭터 핑계:              temp=0.7, freq_penalty=0.8, pres_penalty=0.6
운세 생성:                temp=0.75, freq_penalty=0.6, pres_penalty=0.65
웹소설 (일반):            temp=0.9, freq_penalty=0.4, pres_penalty=0.7

// Structured Output으로 포맷 강제
response_format: { type: &quot;json_schema&quot;, json_schema: {...} }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 6: TECH STACK --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TECH STACK&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언어&lt;/b&gt;: TypeScript 5.3.3&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IaC&lt;/b&gt;: AWS CDK v2 (aws-cdk-lib 2.203.1)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;런타임&lt;/b&gt;: Node.js 18.x (AWS Lambda)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API&lt;/b&gt;: AWS API Gateway REST API (20 req/sec rate, 40 burst)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB&lt;/b&gt;: AWS DynamoDB (Single Table Design, 6 GSI, TTL, PAY_PER_REQUEST)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI&lt;/b&gt;: OpenAI GPT API (gpt-4o-mini, gpt-4.1-mini)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인증&lt;/b&gt;: Kakao/Apple OAuth + JWT (jsonwebtoken 9.0.2)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스케줄러&lt;/b&gt;: AWS EventBridge (cron 매일 자정)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보안&lt;/b&gt;: AWS Secrets Manager (OpenAI API Key, Kakao Admin Key)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빌드&lt;/b&gt;: Webpack 5 (멀티 엔트리, Lambda 개별 번들링)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://docs.aws.amazon.com/cdk/v2/guide/home.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS CDK v2 Guide&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DynamoDB Single Table Design&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://platform.openai.com/docs/guides/structured-outputs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OpenAI Structured Outputs&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS Lambda Runtimes&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS CDK&lt;/b&gt;를 활용하여 완전한 서버리스 백엔드를 구축했습니다. TypeScript로 인프라와 비즈니스 로직을 동일하게 관리할 수 있어 개발 생산성이 크게 향상되었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;DynamoDB Single Table Design GSI&lt;/b&gt;로 엔티티의 다양한 접근 패턴을 효율적으로 처리하면서도 핫 파티션 없이 안정적으로 운영할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenAI GPT API&lt;/b&gt; 연동 시 Temperature보다 &lt;b&gt;Frequency/Presence Penalty&lt;/b&gt;가 창의적 다양성에 더 큰 영향을 미친다는 것을 발견했고, Structured Output을 활용해 포맷 일관성도 확보했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트를 통해 서버리스 아키텍처의 장점과 DynamoDB 설계의 중요성을 깊이 이해할 수 있었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 스토어 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/boA0dC/dJMcaf6wJ7C/3NdpMM00UkxL6p5uZNHnl1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bBTKfv/dJMcaiIUog5/eaELx3KRsITmULgQITHEdK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cxsCJY/dJMcaiIUog4/W7KyDNdZrI6IGoE1ee2wO1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cJrsfO/dJMcac2ZBuA/CB9UvjLXW9DqLHtjCyRDC0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cRKPIL/dJMcaf6wJ7F/bygJsPkk1Whn2X3LPNPh6K/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/FlO4B/dJMcac2ZBuB/3cL5AD1JPqCBQt9HAwAjy1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/mfb6o/dJMcaf6wJ7E/y3wZzMDCrQir4HEev7C0ak/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boA0dC/dJMcaf6wJ7C/3NdpMM00UkxL6p5uZNHnl1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/boA0dC/dJMcaf6wJ7C/3NdpMM00UkxL6p5uZNHnl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boA0dC/dJMcaf6wJ7C/3NdpMM00UkxL6p5uZNHnl1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboA0dC%2FdJMcaf6wJ7C%2F3NdpMM00UkxL6p5uZNHnl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBTKfv/dJMcaiIUog5/eaELx3KRsITmULgQITHEdK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bBTKfv/dJMcaiIUog5/eaELx3KRsITmULgQITHEdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBTKfv/dJMcaiIUog5/eaELx3KRsITmULgQITHEdK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBTKfv%2FdJMcaiIUog5%2FeaELx3KRsITmULgQITHEdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxsCJY/dJMcaiIUog4/W7KyDNdZrI6IGoE1ee2wO1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cxsCJY/dJMcaiIUog4/W7KyDNdZrI6IGoE1ee2wO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxsCJY/dJMcaiIUog4/W7KyDNdZrI6IGoE1ee2wO1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxsCJY%2FdJMcaiIUog4%2FW7KyDNdZrI6IGoE1ee2wO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJrsfO/dJMcac2ZBuA/CB9UvjLXW9DqLHtjCyRDC0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cJrsfO/dJMcac2ZBuA/CB9UvjLXW9DqLHtjCyRDC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJrsfO/dJMcac2ZBuA/CB9UvjLXW9DqLHtjCyRDC0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJrsfO%2FdJMcac2ZBuA%2FCB9UvjLXW9DqLHtjCyRDC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRKPIL/dJMcaf6wJ7F/bygJsPkk1Whn2X3LPNPh6K/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cRKPIL/dJMcaf6wJ7F/bygJsPkk1Whn2X3LPNPh6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRKPIL/dJMcaf6wJ7F/bygJsPkk1Whn2X3LPNPh6K/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRKPIL%2FdJMcaf6wJ7F%2FbygJsPkk1Whn2X3LPNPh6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FlO4B/dJMcac2ZBuB/3cL5AD1JPqCBQt9HAwAjy1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/FlO4B/dJMcac2ZBuB/3cL5AD1JPqCBQt9HAwAjy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FlO4B/dJMcac2ZBuB/3cL5AD1JPqCBQt9HAwAjy1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFlO4B%2FdJMcac2ZBuB%2F3cL5AD1JPqCBQt9HAwAjy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mfb6o/dJMcaf6wJ7E/y3wZzMDCrQir4HEev7C0ak/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/mfb6o/dJMcaf6wJ7E/y3wZzMDCrQir4HEev7C0ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mfb6o/dJMcaf6wJ7E/y3wZzMDCrQir4HEev7C0ak/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmfb6o%2FdJMcaf6wJ7E%2Fy3wZzMDCrQir4HEev7C0ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonback.kxcuse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6751330034&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;처음 서버리스 아키텍처를 설계할 때는 DynamoDB의 Single Table Design이 가장 큰 도전이었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;관계형 DB에서는 테이블을 나누고 JOIN하면 되지만, DynamoDB에서는 &lt;b&gt;모든 접근 패턴을 미리 파악&lt;/b&gt;하고 &lt;b&gt;GSI를 설계&lt;/b&gt;해야 합니다. GSI를 설계하면서 &quot;이 쿼리는 어떤 인덱스를 탈까?&quot;를 수십 번 고민했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;또한 OpenAI GPT API를 실제 서비스에 연동하면서 &lt;b&gt;프롬프트 엔지니어링&lt;/b&gt;의 중요성을 깨달았습니다. 특히 한국어 콘텐츠 생성에서는 영어와 다른 파라미터 튜닝이 필요하다는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;이 경험이 서버리스 아키텍처나 AI 서비스 개발을 고민하시는 분들께 도움이 되었으면 합니다!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/백엔드</category>
      <category>api gateway</category>
      <category>AWS CDK</category>
      <category>aws lambda</category>
      <category>dynamodb</category>
      <category>EventBridge</category>
      <category>OpenAI GPT</category>
      <category>Prompt Engineering</category>
      <category>Single Table Design</category>
      <category>TypeScript</category>
      <category>서버리스 아키텍처</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/589</guid>
      <comments>https://kwonputer.tistory.com/589#entry589comment</comments>
      <pubDate>Mon, 2 Feb 2026 12:43:22 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AI 핑계 생성기 '뻥이야' 앱 개발기</title>
      <link>https://kwonputer.tistory.com/588</link>
      <description>&lt;!-- 스토어 링크 배너 (상단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/nZ9RS/dJMcajnu1Hy/f3gKDWo2uFvyNoZ4ikB630/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/v25W8/dJMcahJ30Hs/p6F312QTlB5wH0KaucK7TK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ccG38m/dJMb996jR9d/KuaCbpVbI5aiGWi2bvrxQ1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cDye6X/dJMb996jR9b/w71dymYyMNJh9dKMRcl5X0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/C0I7o/dJMcad1Tc2q/aZkCVmGtSyIkqKQxYBZJyk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/btgs6x/dJMb996jR9c/HKrQG3i2qIWgetuloPqdT1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/Fvy9u/dJMcajnu1Hz/3DAZTOxnioVKC9KYfNh9P0/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZ9RS/dJMcajnu1Hy/f3gKDWo2uFvyNoZ4ikB630/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/nZ9RS/dJMcajnu1Hy/f3gKDWo2uFvyNoZ4ikB630/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZ9RS/dJMcajnu1Hy/f3gKDWo2uFvyNoZ4ikB630/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZ9RS%2FdJMcajnu1Hy%2Ff3gKDWo2uFvyNoZ4ikB630%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v25W8/dJMcahJ30Hs/p6F312QTlB5wH0KaucK7TK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/v25W8/dJMcahJ30Hs/p6F312QTlB5wH0KaucK7TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v25W8/dJMcahJ30Hs/p6F312QTlB5wH0KaucK7TK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv25W8%2FdJMcahJ30Hs%2Fp6F312QTlB5wH0KaucK7TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccG38m/dJMb996jR9d/KuaCbpVbI5aiGWi2bvrxQ1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ccG38m/dJMb996jR9d/KuaCbpVbI5aiGWi2bvrxQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccG38m/dJMb996jR9d/KuaCbpVbI5aiGWi2bvrxQ1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccG38m%2FdJMb996jR9d%2FKuaCbpVbI5aiGWi2bvrxQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDye6X/dJMb996jR9b/w71dymYyMNJh9dKMRcl5X0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cDye6X/dJMb996jR9b/w71dymYyMNJh9dKMRcl5X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDye6X/dJMb996jR9b/w71dymYyMNJh9dKMRcl5X0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDye6X%2FdJMb996jR9b%2Fw71dymYyMNJh9dKMRcl5X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C0I7o/dJMcad1Tc2q/aZkCVmGtSyIkqKQxYBZJyk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/C0I7o/dJMcad1Tc2q/aZkCVmGtSyIkqKQxYBZJyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C0I7o/dJMcad1Tc2q/aZkCVmGtSyIkqKQxYBZJyk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC0I7o%2FdJMcad1Tc2q%2FaZkCVmGtSyIkqKQxYBZJyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btgs6x/dJMb996jR9c/HKrQG3i2qIWgetuloPqdT1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/btgs6x/dJMb996jR9c/HKrQG3i2qIWgetuloPqdT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btgs6x/dJMb996jR9c/HKrQG3i2qIWgetuloPqdT1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtgs6x%2FdJMb996jR9c%2FHKrQG3i2qIWgetuloPqdT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fvy9u/dJMcajnu1Hz/3DAZTOxnioVKC9KYfNh9P0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/Fvy9u/dJMcajnu1Hz/3DAZTOxnioVKC9KYfNh9P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fvy9u/dJMcajnu1Hz/3DAZTOxnioVKC9KYfNh9P0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFvy9u%2FdJMcajnu1Hz%2F3DAZTOxnioVKC9KYfNh9P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonback.kxcuse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6751330034&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 요약 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #d0d9ff; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;SUMMARY&lt;/p&gt;
&lt;p style=&quot;color: #fff; font-size: 22px; font-weight: bold; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야 (Kxcuse) - AI 핑계 생성 Flutter 앱&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;MZ세대를 위한 AI 기반 창의적 핑계/변명 생성 모바일 앱 개발기입니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 4px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Clean Architecture + MVVM + Riverpod&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GoRouter + KRouter&lt;/b&gt; 선언형 라우팅, &lt;b&gt;Hive DB&lt;/b&gt; 차등 라이프사이클 관리.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 15px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기술적 도전: &lt;b&gt;AbusePrevention 영구 DB&lt;/b&gt;, &lt;b&gt;자동 토큰 갱신 시스템&lt;/b&gt;, &lt;b&gt;크로스 페이지 데이터 동기화&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 24px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 목차 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 28px 32px; text-align: center;&quot;&gt;
&lt;p style=&quot;color: #868e96; font-size: 11px; letter-spacing: 2px; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;TABLE OF CONTENTS&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;왜 이 앱을 만들었나?&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;아키텍처 설계&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;주요 기능&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;기술적 도전과 해결&lt;/span&gt; &lt;span style=&quot;background-color: #fff; border: 1px solid #dee2e6; padding: 8px 16px; border-radius: 20px; font-size: 13px; color: #495057; display: inline-block; margin: 4px;&quot;&gt;핵심 코드&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 1: WHY --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;WHY&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이 앱을 만들었나?&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;오늘 왜 늦었어?&quot;, &quot;왜 약속에 못 와?&quot; - 일상에서 마주하는 순간마다 &lt;b&gt;재치 있는 핑계&lt;/b&gt;가 필요할 때가 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;단순한 핑계 생성기가 아닌, &lt;b&gt;AI가 상황을 분석&lt;/b&gt;하고 &lt;b&gt;캐릭터 페르소나&lt;/b&gt;와 &lt;b&gt;말투(Tone)&lt;/b&gt;를 반영한 창의적인 핑계를 만들어주는 앱을 기획했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로는 Clean Architecture의 레이어 분리, 보안 데이터의 영구 보존, 앱 라이프사이클과 연동된 토큰 관리 등 &lt;b&gt;실서비스 수준의 아키텍처&lt;/b&gt;를 구현하고 싶었습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 2: ARCHITECTURE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;ARCHITECTURE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;아키텍처 설계&lt;/p&gt;
&lt;!-- Clean Architecture 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;Clean Architecture + MVVM + Riverpod&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Presentation Layer&lt;/b&gt; &amp;mdash; pages, viewmodels, widgets, styles&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain Layer&lt;/b&gt; &amp;mdash; models, repositories interface, usecases&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data Layer&lt;/b&gt; &amp;mdash; repositories impl, services, dbs, configs&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- DI 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;GetIt + Injectable 의존성 주입&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 생성 기반 DI&lt;/b&gt; &amp;mdash; build_runner로 DI 코드 자동 생성, 런타임 오류 방지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;싱글톤/팩토리 패턴&lt;/b&gt; &amp;mdash; Service는 싱글톤, ViewModel은 팩토리로 생명주기 관리&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 라우팅 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;GoRouter + KRouter 네비게이션 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GoRouter 선언형 라우팅&lt;/b&gt; &amp;mdash; Scheme enum 기반 타입 안전 라우트, go_transitions 애니메이션&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;KRouter 래퍼&lt;/b&gt; &amp;mdash; 인증 가드(isUseLogin), 다이얼로그 상태 추적, 중복 라우트 방지, Kakao 딥링크 필터링&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 3: FEATURES --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;FEATURES&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;주요 기능&lt;/p&gt;
&lt;!-- 토큰 관리 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;TokenManagerService - 자동 토큰 갱신&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선제적 갱신&lt;/b&gt; &amp;mdash; 만료 24시간 전 자동 갱신, 30분 주기 Timer 기반 검증&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라이프사이클 연동&lt;/b&gt; &amp;mdash; 앱 resume 시 토큰 유효성 즉시 재검증&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dio 인터셉터&lt;/b&gt; &amp;mdash; 모든 API 요청에 자동 토큰 주입&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- Hive DB 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #20c997; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;Hive 4개 DB - 차등 라이프사이클 관리&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserSettingsDatabase&lt;/b&gt; &amp;mdash; 토큰, 테마 설정 (로그아웃 시 초기화)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CacheDatabase&lt;/b&gt; &amp;mdash; API 응답 캐시 (로그아웃 시 초기화)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MemorialDatabase&lt;/b&gt; &amp;mdash; 핑계 목록 로컬 저장&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AbusePreventionDatabase&lt;/b&gt; &amp;mdash; 가입 보너스/출석 추적 (영구 보존, 탈퇴 시에도 삭제 금지)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 동기화 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #fd7e14; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;BackgroundUpdateService + CRUD Sync Event&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;백그라운드 동기화&lt;/b&gt; &amp;mdash; 비활성 페이지 데이터 자동 갱신 (활성 시 취소)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CRUD Sync Event&lt;/b&gt; &amp;mdash; BaseViewModel.syncCRUDEvent()로 페이지 간 데이터 일관성&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PointProviders&lt;/b&gt; &amp;mdash; Riverpod 반응형 포인트 상태 전역 관리&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 16px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 테마 카드 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-left: 4px solid #667eea; border-radius: 0 12px 12px 0; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 17px; font-weight: bold; color: #212529; padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;KColors 동적 테마 시스템&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3가지 테마 모드&lt;/b&gt; &amp;mdash; 라이트 / 다크 / 시스템 자동 감지&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 10px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;KColors() 싱글톤&lt;/b&gt; &amp;mdash; isDarkTheme 플래그 기반 동적 색상 반환&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pretendard 커스텀 폰트&lt;/b&gt; &amp;mdash; 9웨이트 (Thin 100 ~ Black 900)&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 4: TROUBLESHOOTING --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TROUBLESHOOTING&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술적 도전과 해결&lt;/p&gt;
&lt;!-- 문제 1 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 01&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;로그아웃/탈퇴 시 보안 데이터와 사용자 데이터의 차등 초기화&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;로그아웃 시 토큰과 캐시는 삭제해야 하지만, &lt;b&gt;가입 보너스 악용 방지를 위한 AbusePrevention 데이터는 영구 보존&lt;/b&gt;해야 했습니다. 계정 탈퇴 후 재가입해도 보너스를 중복 수령하면 안 됩니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 8px; padding: 16px; margin-bottom: 16px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;문제 상황&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;단순한 전체 DB clear()로는 보안 데이터까지 삭제되어, 재가입 시 200pt 가입 보너스를 무한 수령할 수 있는 취약점 발생.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - Hive DB 4개 분리 + 차등 초기화 로직&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// KRouter.offAllWithLogout() 차등 초기화
Future&amp;lt;void&amp;gt; offAllWithLogout() async {
  // 사용자 데이터 초기화 (세션 종료)
  await UserSettingsDatabase.clear();
  await CacheDatabase.clear();

  // AbusePrevention은 절대 clear() 호출 금지!
  // 가입 보너스, 출석 체크 등 악용 방지 데이터 영구 보존

  clearAllViewModels();
  forceCloseAllDialogs();
  go(Scheme.login);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 2 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 02&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;페이지 간 데이터 불일치 (크로스 페이지 동기화)&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;핑계 생성 후 목록 페이지로 돌아갔을 때 &lt;b&gt;새 핑계가 표시되지 않거나&lt;/b&gt;, 포인트 차감이 다른 페이지에 반영되지 않는 문제가 발생했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 8px; padding: 16px; margin-bottom: 16px;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; padding-bottom: 8px;&quot; data-ke-size=&quot;size16&quot;&gt;문제 상황&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057;&quot; data-ke-size=&quot;size16&quot;&gt;각 페이지의 ViewModel이 독립적으로 데이터를 로드/캐싱하므로, 한 페이지에서의 CRUD 변경이 다른 페이지에 자동 전파되지 않음.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - BaseViewModel CRUD Sync Event 패턴&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// BaseViewModel.dart
abstract class BaseViewModel {
  // 페이지 간 CRUD 이벤트 동기화
  void syncCRUDEvent(CRUDEventType type, dynamic data) {
    // 모든 활성 ViewModel에 이벤트 전파
    _registeredViewModels.forEach((vm) =&amp;gt; vm.onCRUDEvent(type, data));
  }
}

// KRouter.back() 호출 시 자동 동기화
Future&amp;lt;void&amp;gt; back() async {
  await syncWithMemorialDb();  // 로컬 DB 동기화
  _notifyActiveViewModels();   // ViewModel 갱신 트리거
  GoRouter.of(context).pop();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 20px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 문제 3 --&gt;
&lt;div style=&quot;background-color: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;padding-bottom: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fff0f0; color: #e03131; font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 4px; letter-spacing: 1px;&quot;&gt;PROBLEM 03&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 18px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;토큰 만료로 인한 갑작스러운 로그아웃&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #495057; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 앱을 사용하던 중 &lt;b&gt;토큰이 만료되어 갑자기 로그인 화면으로 이동&lt;/b&gt;하는 UX 문제가 발생했습니다. 특히 백그라운드에서 앱이 오래 있다가 포그라운드로 돌아올 때 자주 발생했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #d3f9d8; border-radius: 8px; padding: 16px 20px;&quot;&gt;
&lt;p style=&quot;color: #2b8a3e; font-size: 13px; font-weight: bold; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;SOLUTION - TokenManagerService 선제적 갱신 + 라이프사이클 연동&lt;/p&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// TokenManagerService.dart
class TokenManagerService {
  Timer? _refreshTimer;

  void startAutoRefresh() {
    // 30분마다 토큰 상태 검증
    _refreshTimer = Timer.periodic(Duration(minutes: 30), (_) {
      _checkAndRefreshIfNeeded();
    });
  }

  Future&amp;lt;void&amp;gt; _checkAndRefreshIfNeeded() async {
    final expiresAt = await _db.getTokenExpiry();
    final remaining = expiresAt.difference(DateTime.now());

    // 만료 24시간 전에 선제적 갱신
    if (remaining &amp;lt; Duration(hours: 24)) {
      await refreshToken();
    }
  }
}

// main.dart - 앱 라이프사이클 훅
WidgetsBinding.instance.addObserver(
  LifecycleEventHandler(
    onResume: () =&amp;gt; TokenManagerService.validateAndRefresh(),
  ),
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 5: CORE CODE --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CORE CODE&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 코드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;1. KRouter 네비게이션 래퍼&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// KRouter.dart - 인증 가드 + 다이얼로그 관리
class KRouter {
  static final Set&amp;lt;Dialog&amp;gt; _activeDialogs = {};

  static Future&amp;lt;void&amp;gt; to(Scheme scheme, {Object? extra}) async {
    // 인증 필요 라우트 체크
    if (scheme.isUseLogin &amp;amp;&amp;amp; !await _isLoggedIn()) {
      return go(Scheme.login);
    }

    // Kakao OAuth 딥링크 필터링
    if (_isKakaoDeepLink(scheme)) return;

    // 중복 라우트 방지
    if (_currentRoute == scheme.path) return;

    GoRouter.of(_context).push(scheme.path, extra: extra);
  }

  static void forceCloseAllDialogs() {
    for (final dialog in _activeDialogs.toList()) {
      Navigator.of(_context).pop();
    }
    _activeDialogs.clear();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;2. Riverpod ViewModel Provider&lt;/p&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// providers/excuse_providers.dart
@riverpod
class ExcuseListViewModel extends _$ExcuseListViewModel {
  @override
  AsyncValue&amp;lt;List&amp;lt;Excuse&amp;gt;&amp;gt; build() =&amp;gt; const AsyncValue.loading();

  Future&amp;lt;void&amp;gt; loadExcuses() async {
    state = const AsyncValue.loading();
    try {
      final useCase = ref.read(getExcuseListUseCaseProvider);
      final excuses = await useCase.execute();
      state = AsyncValue.data(excuses);
    } catch (e, st) {
      state = AsyncValue.error(e, st);
    }
  }

  // CRUD Sync Event 수신
  void onCRUDEvent(CRUDEventType type, Excuse excuse) {
    if (type == CRUDEventType.created) {
      state = state.whenData((list) =&amp;gt; [excuse, ...list]);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 28px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;font-size: 16px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;3. KColors 동적 테마&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;// styles/KColors.dart
class KColors {
  static final KColors _instance = KColors._();
  factory KColors() =&amp;gt; _instance;
  KColors._();

  bool isDarkTheme = false;

  Color get primary =&amp;gt; isDarkTheme ? Color(0xFF667EEA) : Color(0xFF5A67D8);
  Color get background =&amp;gt; isDarkTheme ? Color(0xFF1A1A2E) : Color(0xFFF7FAFC);
  Color get textPrimary =&amp;gt; isDarkTheme ? Color(0xFFE2E8F0) : Color(0xFF2D3748);

  void updateTheme(AppThemeTypes type) {
    switch (type) {
      case AppThemeTypes.light: isDarkTheme = false;
      case AppThemeTypes.dark: isDarkTheme = true;
      case AppThemeTypes.system:
        isDarkTheme = WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 섹션 6: TECH STACK --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;TECH STACK&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 12px; padding: 24px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Core Framework&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;Flutter 3.32.1&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;Dart SDK ^3.5.4&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;State Management &amp;amp; DI&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;flutter_riverpod 2.6.1&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;riverpod_annotation 2.6.1&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;get_it 8.0.3&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;injectable 2.5.0&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Routing &amp;amp; Navigation&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;go_router 15.1.2&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;go_transitions 0.8.1&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Local Storage &amp;amp; Network&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;hive 2.2.3&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;dio 5.8.0&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Authentication&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;kakao_flutter_sdk 1.9.7&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;sign_in_with_apple 7.0.1&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Firebase &amp;amp; Analytics&lt;/p&gt;
&lt;p style=&quot;padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;firebase_core 3.4.0&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;firebase_crashlytics 4.3.6&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;firebase_remote_config&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; font-weight: 600; color: #212529; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Animation &amp;amp; UI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;flutter_animate 4.5.2&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;lottie 3.2.0&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;shimmer 3.0.0&lt;/code&gt; &lt;code style=&quot;background-color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 12px; color: #495057; border: 1px solid #e9ecef;&quot;&gt;Pretendard 9 weights&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 참고자료 --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border-radius: 10px; padding: 20px 24px;&quot;&gt;
&lt;p style=&quot;font-size: 11px; color: #868e96; letter-spacing: 2px; font-weight: 600; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;REFERENCE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://docs.flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Documentation&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://riverpod.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Riverpod&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/go_router&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GoRouter&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://pub.dev/packages/hive&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hive&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #fff; border: 1px solid #dee2e6; padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #667eea; text-decoration: none; margin: 4px;&quot; href=&quot;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Clean Architecture&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 60px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 결론 --&gt;
&lt;p style=&quot;color: #667eea; font-size: 11px; letter-spacing: 2px; font-weight: 600;&quot; data-ke-size=&quot;size16&quot;&gt;CONCLUSION&lt;/p&gt;
&lt;p style=&quot;font-size: 24px; font-weight: bold; color: #212529; padding-top: 4px; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;결론&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;뻥이야&lt;/b&gt;는 단순한 핑계 생성 앱을 넘어, &lt;b&gt;실서비스 수준의 아키텍처&lt;/b&gt;를 구현한 Flutter 프로젝트입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;Clean Architecture의 엄격한 레이어 분리, Hive DB의 차등 라이프사이클 관리, TokenManagerService의 선제적 토큰 갱신, KRouter의 인증 가드와 다이얼로그 관리 등 &lt;b&gt;실무에서 마주하는 복잡한 요구사항&lt;/b&gt;들을 해결했습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; color: #495057; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;AbusePrevention 영구 DB&lt;/b&gt;를 통한 보안 데이터 보호와 &lt;b&gt;CRUD Sync Event 패턴&lt;/b&gt;을 통한 크로스 페이지 데이터 일관성은 이 프로젝트의 핵심 설계입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 스토어 링크 배너 (하단) --&gt;
&lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; padding: 20px 24px; text-align: center;&quot;&gt;
&lt;p style=&quot;font-size: 13px; color: #868e96; font-weight: 600; letter-spacing: 1px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;DOWNLOAD&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;뻥이야 다운로드&lt;/p&gt;
&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/VNVEa/dJMcafSZ2Du/pWftraIu2HiZFAHwOdpb5k/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/rFvCN/dJMcaia74Pn/EZKb0sW5ymGqXXuk24ZHwK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/br0cXW/dJMcaia74Pm/sA1Gob26zMk2uxcEip24r0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/UfZdh/dJMcaia74Pl/LNmo4kYwjMJ2zaIHOeASHk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/XfkFA/dJMcaiWr7BU/FeNL6jnWuyeBzHO9kdgk4K/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/blUAwL/dJMcaiWr7BT/mg63ZsNyxMRkiCyu82Nvk1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/GxPzk/dJMcaiWr7BS/7bofk6ZawrLq5u3xgng26k/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VNVEa/dJMcafSZ2Du/pWftraIu2HiZFAHwOdpb5k/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/VNVEa/dJMcafSZ2Du/pWftraIu2HiZFAHwOdpb5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VNVEa/dJMcafSZ2Du/pWftraIu2HiZFAHwOdpb5k/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVNVEa%2FdJMcafSZ2Du%2FpWftraIu2HiZFAHwOdpb5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rFvCN/dJMcaia74Pn/EZKb0sW5ymGqXXuk24ZHwK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/rFvCN/dJMcaia74Pn/EZKb0sW5ymGqXXuk24ZHwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rFvCN/dJMcaia74Pn/EZKb0sW5ymGqXXuk24ZHwK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrFvCN%2FdJMcaia74Pn%2FEZKb0sW5ymGqXXuk24ZHwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br0cXW/dJMcaia74Pm/sA1Gob26zMk2uxcEip24r0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/br0cXW/dJMcaia74Pm/sA1Gob26zMk2uxcEip24r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br0cXW/dJMcaia74Pm/sA1Gob26zMk2uxcEip24r0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr0cXW%2FdJMcaia74Pm%2FsA1Gob26zMk2uxcEip24r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UfZdh/dJMcaia74Pl/LNmo4kYwjMJ2zaIHOeASHk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/UfZdh/dJMcaia74Pl/LNmo4kYwjMJ2zaIHOeASHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UfZdh/dJMcaia74Pl/LNmo4kYwjMJ2zaIHOeASHk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUfZdh%2FdJMcaia74Pl%2FLNmo4kYwjMJ2zaIHOeASHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XfkFA/dJMcaiWr7BU/FeNL6jnWuyeBzHO9kdgk4K/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/XfkFA/dJMcaiWr7BU/FeNL6jnWuyeBzHO9kdgk4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XfkFA/dJMcaiWr7BU/FeNL6jnWuyeBzHO9kdgk4K/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXfkFA%2FdJMcaiWr7BU%2FFeNL6jnWuyeBzHO9kdgk4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blUAwL/dJMcaiWr7BT/mg63ZsNyxMRkiCyu82Nvk1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/blUAwL/dJMcaiWr7BT/mg63ZsNyxMRkiCyu82Nvk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blUAwL/dJMcaiWr7BT/mg63ZsNyxMRkiCyu82Nvk1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblUAwL%2FdJMcaiWr7BT%2Fmg63ZsNyxMRkiCyu82Nvk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GxPzk/dJMcaiWr7BS/7bofk6ZawrLq5u3xgng26k/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/GxPzk/dJMcaiWr7BS/7bofk6ZawrLq5u3xgng26k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GxPzk/dJMcaiWr7BS/7bofk6ZawrLq5u3xgng26k/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGxPzk%2FdJMcaiWr7BS%2F7bofk6ZawrLq5u3xgng26k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;font-size: 16px; font-weight: bold; color: #212529; padding-bottom: 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;display: inline-block; background-color: #20c997; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://play.google.com/store/apps/details?id=com.kwon.kwonback.kxcuse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt; &lt;a style=&quot;display: inline-block; background-color: #495057; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; margin: 4px;&quot; href=&quot;https://apps.apple.com/kr/app/id6751330034&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; height: 40px; background-color: transparent;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- 마무리 카드 --&gt;
&lt;div style=&quot;background-color: #667eea; border-radius: 16px; padding: 28px 32px;&quot;&gt;
&lt;p style=&quot;color: #fff; font-size: 18px; font-weight: bold; padding-bottom: 20px;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;처음 이 앱을 기획했을 때는 &quot;재미있는 핑계 생성기&quot;를 만들고 싶었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 개발을 진행하면서 토큰 만료 문제, 보안 데이터 보호, 페이지 간 데이터 동기화 등 예상치 못한 도전들을 마주하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 12px;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &quot;로그아웃해도 삭제되면 안 되는 데이터&quot;와 &quot;삭제되어야 하는 데이터&quot;를 어떻게 분리할 것인가에 대한 고민은 &lt;b&gt;Hive DB 차등 라이프사이클 관리&lt;/b&gt;라는 해결책으로 이어졌습니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px; padding-bottom: 6px;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트가 Flutter로 실서비스를 개발하시는 분들께 작은 도움이 되었으면 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #f0f3ff; font-size: 14px;&quot; data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 뻥이야의 &lt;b&gt;AWS Serverless 백엔드&lt;/b&gt; (Lambda + DynamoDB + OpenAI GPT)에 대해 다루겠습니다!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>개발/프로젝트</category>
      <category>Clean Architecture</category>
      <category>flutter</category>
      <category>getit</category>
      <category>gorouter</category>
      <category>injectable</category>
      <category>Kakao 로그인</category>
      <category>MVVM</category>
      <category>riverpod</category>
      <category>토큰 관리</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/588</guid>
      <comments>https://kwonputer.tistory.com/588#entry588comment</comments>
      <pubDate>Mon, 2 Feb 2026 12:34:10 +0900</pubDate>
    </item>
    <item>
      <title>Mac CDR 개발</title>
      <link>https://kwonputer.tistory.com/586</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OQ3tY/dJMcaajNKVI/vrka8zWzs3lv2AQfu7k3hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OQ3tY/dJMcaajNKVI/vrka8zWzs3lv2AQfu7k3hK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;809&quot; data-filename=&quot;CDR0.png&quot; width=&quot;341&quot; style=&quot;width: 49.1701%; margin-right: 10px;&quot; data-widthpercent=&quot;49.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OQ3tY/dJMcaajNKVI/vrka8zWzs3lv2AQfu7k3hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOQ3tY%2FdJMcaajNKVI%2Fvrka8zWzs3lv2AQfu7k3hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;942&quot; height=&quot;809&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sbcSl/dJMcaajNKVH/wc2M3oByHY4s3fGlgzKkGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sbcSl/dJMcaajNKVH/wc2M3oByHY4s3fGlgzKkGk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;789&quot; data-filename=&quot;CDR1.png&quot; width=&quot;336&quot; height=&quot;286&quot; style=&quot;width: 49.6672%;&quot; data-widthpercent=&quot;50.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sbcSl/dJMcaajNKVH/wc2M3oByHY4s3fGlgzKkGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsbcSl%2FdJMcaajNKVH%2Fwc2M3oByHY4s3fGlgzKkGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;928&quot; height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 오랜만에 예전에 만든 프로그램을 발견해서, 간단하게 운영체제 지원만 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 위 이미지처럼 Windows 전용으로 만들었던 CDR 프로그램을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지처럼 Mac도 지원하도록 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows: &lt;a href=&quot;https://drive.google.com/file/d/1IO0Po1u7tKdJp5XlfvEhDi8AzvByF90F/view?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://drive.google.com/file/d/1IO0Po1u7tKdJp5XlfvEhDi8AzvByF90F/view?usp=sharing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac: &lt;a href=&quot;https://drive.google.com/file/d/1KOKaffYgFxlJG_gv3nuVLVaRoHTMQd_k/view?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://drive.google.com/file/d/1KOKaffYgFxlJG_gv3nuVLVaRoHTMQd_k/view?usp=sharing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;CDR2.jpg&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfyk0f/dJMb996gJgN/ZdcjW0n8H1EZDOftDmkMSK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfyk0f/dJMb996gJgN/ZdcjW0n8H1EZDOftDmkMSK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfyk0f/dJMb996gJgN/ZdcjW0n8H1EZDOftDmkMSK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfyk0f%2FdJMb996gJgN%2FZdcjW0n8H1EZDOftDmkMSK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1158&quot; height=&quot;941&quot; data-filename=&quot;CDR2.jpg&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT &amp;middot; 테크/도구 &amp;middot; 생산성</category>
      <category>CDR</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/586</guid>
      <comments>https://kwonputer.tistory.com/586#entry586comment</comments>
      <pubDate>Tue, 27 Jan 2026 09:55:55 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Project] 간단한 녹음 앱</title>
      <link>https://kwonputer.tistory.com/582</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;안녕하세요!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;오늘도 블로그를 찾아주셔서 감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 글에서는 간단한 녹음 기능이 있는 앱을 개발해 보려고 합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;먼저 프로젝트부터 생성해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uXEAi/btsMMnHxz2j/Eyz7nR41PCKppCBUJ31nRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uXEAi/btsMMnHxz2j/Eyz7nR41PCKppCBUJ31nRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uXEAi/btsMMnHxz2j/Eyz7nR41PCKppCBUJ31nRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuXEAi%2FbtsMMnHxz2j%2FEyz7nR41PCKppCBUJ31nRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;795&quot; height=&quot;726&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 pubspec.yaml에 필요한 라이브러리를 선언해 주고 import 해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csl4kf/btsMMngpBde/GvU2piP8QacFEzqMzK4DEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csl4kf/btsMMngpBde/GvU2piP8QacFEzqMzK4DEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csl4kf/btsMMngpBde/GvU2piP8QacFEzqMzK4DEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsl4kf%2FbtsMMngpBde%2FGvU2piP8QacFEzqMzK4DEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;708&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 시나리오는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 녹음 기능: 재생, 일시정지, 정지(=저장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 녹음 재생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 녹음 파일명 변경 &amp;amp; 녹음 파일 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 우선 프로젝트 구조는 아래의 이미지처럼 구성해 봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAbZm9/btsML0slCwc/q3fkritb93erdyTMrTLowk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAbZm9/btsML0slCwc/q3fkritb93erdyTMrTLowk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAbZm9/btsML0slCwc/q3fkritb93erdyTMrTLowk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAbZm9%2FbtsML0slCwc%2Fq3fkritb93erdyTMrTLowk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;447&quot; height=&quot;986&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 정도 기본 뼈대는 완성이 됐습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;1268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bACEWP/btsMNBx3133/Ng2RekwVe7eK2VQcI1x8f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bACEWP/btsMNBx3133/Ng2RekwVe7eK2VQcI1x8f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bACEWP/btsMNBx3133/Ng2RekwVe7eK2VQcI1x8f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbACEWP%2FbtsMNBx3133%2FNg2RekwVe7eK2VQcI1x8f0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;911&quot; height=&quot;1268&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;1268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터가 본격적인 개발 시작입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발해야 하는 항목들을 적으면서 생각을 정리해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. pubspec.yaml에 필요한 라이브러리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 녹음 기능을 위한 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 오디오 재생을 위한 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 파일 시스템 접근을 위한 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 권한 관리를 위한 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. 권한 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 마이크 권한 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. RecordData 모델 확장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 파일 경로 &amp;amp; 녹음 날짜 &amp;amp; 녹음 길이 &amp;amp; 파일명... 등의 필요한 속성 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;4. RecordService 클래스 구현&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;5. RecordRepository 클래스 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;6. 권한 요청 로직 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 앱 시작 &amp;amp; 녹음 시작 전에 마이크 권한 요청 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;7. RecordingPage UI 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 녹음 관련 버튼 (시작 &amp;amp; 일시정지 &amp;amp; 정지) 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 녹음 시간 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 녹음 중 시각적 피드백(파형) 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;8. HistoryPage UI 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 녹음 목록 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 각 항목의 삭제 &amp;amp; 이름 변경 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 목록에서 녹음 선택 시, 재생 페이지로 이동 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;9. PlayHistoryPage UI 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 선택한 녹음 파일 재생 컨트롤 (재생 &amp;amp; 일시정지 &amp;amp; 정지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 재생 진행 상태 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 파일 정보 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;10. 바텀 네비게이션 연동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;11. 다크 모드 &amp;amp; 라이트 모드 지원&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;12. 오류 처리 및 예외 상황 대응&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 권한 거부 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 녹음/재생 실패 시, 에러 메시지 표시&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. pubspec.yaml에 필요한 라이브러리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kxn6s/btsMKVyLZHf/7gYDlBWxj1i24zzn0erLSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kxn6s/btsMKVyLZHf/7gYDlBWxj1i24zzn0erLSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kxn6s/btsMKVyLZHf/7gYDlBWxj1i24zzn0erLSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKxn6s%2FbtsMKVyLZHf%2F7gYDlBWxj1i24zzn0erLSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;614&quot; height=&quot;567&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. 권한 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT627U/btsMLyb4l9l/Lm3s7txnD6cYRh2hcwJki1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT627U/btsMLyb4l9l/Lm3s7txnD6cYRh2hcwJki1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT627U/btsMLyb4l9l/Lm3s7txnD6cYRh2hcwJki1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT627U%2FbtsMLyb4l9l%2FLm3s7txnD6cYRh2hcwJki1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;271&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. RecordData 모델 확장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wiBRg/btsMLrYqjit/XTPnDt9NAiC52TWy9OXk30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wiBRg/btsMLrYqjit/XTPnDt9NAiC52TWy9OXk30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wiBRg/btsMLrYqjit/XTPnDt9NAiC52TWy9OXk30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwiBRg%2FbtsMLrYqjit%2FXTPnDt9NAiC52TWy9OXk30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;552&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;4. RecordService 클래스 구현&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg8mlu/btsMLuAS9kI/ClG0y0SpvH3KeJaYhcAzyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg8mlu/btsMLuAS9kI/ClG0y0SpvH3KeJaYhcAzyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg8mlu/btsMLuAS9kI/ClG0y0SpvH3KeJaYhcAzyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg8mlu%2FbtsMLuAS9kI%2FClG0y0SpvH3KeJaYhcAzyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;1330&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;5. RecordRepository 클래스 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;1311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/191sc/btsMNz76cEM/IhaNF1WOudVdlLtmQLxzo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/191sc/btsMNz76cEM/IhaNF1WOudVdlLtmQLxzo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/191sc/btsMNz76cEM/IhaNF1WOudVdlLtmQLxzo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F191sc%2FbtsMNz76cEM%2FIhaNF1WOudVdlLtmQLxzo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;1311&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;1311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;6. 권한 요청 로직 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WrIkL/btsMMomaFkW/VsavykKwUCXwQHvyRCAkN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WrIkL/btsMMomaFkW/VsavykKwUCXwQHvyRCAkN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WrIkL/btsMMomaFkW/VsavykKwUCXwQHvyRCAkN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWrIkL%2FbtsMMomaFkW%2FVsavykKwUCXwQHvyRCAkN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1154&quot; height=&quot;1330&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sTXft/btsMM5Gp7WI/7K6uYoGv2LzbpAjHBiuzH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sTXft/btsMM5Gp7WI/7K6uYoGv2LzbpAjHBiuzH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sTXft/btsMM5Gp7WI/7K6uYoGv2LzbpAjHBiuzH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsTXft%2FbtsMM5Gp7WI%2F7K6uYoGv2LzbpAjHBiuzH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;815&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;7. RecordingPage UI 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;1309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAVaU/btsMMFOLWtL/PK6zw85Lrs9OqV0U68Up2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAVaU/btsMMFOLWtL/PK6zw85Lrs9OqV0U68Up2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAVaU/btsMMFOLWtL/PK6zw85Lrs9OqV0U68Up2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAVaU%2FbtsMMFOLWtL%2FPK6zw85Lrs9OqV0U68Up2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;758&quot; height=&quot;1309&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;1309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;8. HistoryPage UI 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HtKY2/btsMNynOxa2/UOpytKFFdMBC7p3OXeGNv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HtKY2/btsMNynOxa2/UOpytKFFdMBC7p3OXeGNv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HtKY2/btsMNynOxa2/UOpytKFFdMBC7p3OXeGNv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHtKY2%2FbtsMNynOxa2%2FUOpytKFFdMBC7p3OXeGNv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;1318&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;9. PlayHistoryPage UI 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;1317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLGPe/btsMK8kodQF/r9uFSxR7WOHzVKTtngmr51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLGPe/btsMK8kodQF/r9uFSxR7WOHzVKTtngmr51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLGPe/btsMK8kodQF/r9uFSxR7WOHzVKTtngmr51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBLGPe%2FbtsMK8kodQF%2Fr9uFSxR7WOHzVKTtngmr51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;1317&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;1317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;10. 바텀 네비게이션 연동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sXH8W/btsMLO6O4jN/nGmvXft7sKUAI5jHHRIKrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sXH8W/btsMLO6O4jN/nGmvXft7sKUAI5jHHRIKrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sXH8W/btsMLO6O4jN/nGmvXft7sKUAI5jHHRIKrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsXH8W%2FbtsMLO6O4jN%2FnGmvXft7sKUAI5jHHRIKrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;719&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;11. 다크 모드 &amp;amp; 라이트 모드 지원&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bypax9/btsMMzgLAZv/M3CPITKOAt05NsP2MjvZnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bypax9/btsMMzgLAZv/M3CPITKOAt05NsP2MjvZnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bypax9/btsMMzgLAZv/M3CPITKOAt05NsP2MjvZnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbypax9%2FbtsMMzgLAZv%2FM3CPITKOAt05NsP2MjvZnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;631&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;12. 오류 처리 및 예외 상황 대응&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;995&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjTx80/btsMM7qIWOk/S1oYYxe2x0GOeNrR2gR4dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjTx80/btsMM7qIWOk/S1oYYxe2x0GOeNrR2gR4dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjTx80/btsMM7qIWOk/S1oYYxe2x0GOeNrR2gR4dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjTx80%2FbtsMM7qIWOk%2FS1oYYxe2x0GOeNrR2gR4dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;693&quot; height=&quot;995&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;995&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3VN4r/btsMMaIrrQa/WbjFgcwOQbTFdKokkPUS51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3VN4r/btsMMaIrrQa/WbjFgcwOQbTFdKokkPUS51/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot; data-filename=&quot;KakaoTalk_20250316_213554901.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3VN4r/btsMMaIrrQa/WbjFgcwOQbTFdKokkPUS51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3VN4r%2FbtsMMaIrrQa%2FWbjFgcwOQbTFdKokkPUS51%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3FeOV/btsMLYatZ1b/7KCSHWEJ4dq0jstOACPupk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3FeOV/btsMLYatZ1b/7KCSHWEJ4dq0jstOACPupk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot; data-filename=&quot;KakaoTalk_20250316_213554901_01.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3FeOV/btsMLYatZ1b/7KCSHWEJ4dq0jstOACPupk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3FeOV%2FbtsMLYatZ1b%2F7KCSHWEJ4dq0jstOACPupk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cINMKW/btsMLNfRb2a/ebIJhczRPgrFHzUESPrvoK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cINMKW/btsMLNfRb2a/ebIJhczRPgrFHzUESPrvoK/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot; data-filename=&quot;KakaoTalk_20250316_213554901_02.jpg&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cINMKW/btsMLNfRb2a/ebIJhczRPgrFHzUESPrvoK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcINMKW%2FbtsMLNfRb2a%2FebIJhczRPgrFHzUESPrvoK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Flutter를 통해 간단하게 녹음 앱을 개발해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이번에 여러 라이브러리를 통해 기능을 개별적으로 구현했지만, '&lt;a href=&quot;https://pub.dev/packages/flutter_sound&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Sound 라이브러리&lt;/a&gt;' 이 라이브러리를 사용하시면 더 간단히 녹음기를 구현할 수 있습니다. 다만 라이센스가 'MPL-2.0' 이라서 주의가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 코드를 원하실 경우, 댓글을 남겨주세요.&lt;/p&gt;</description>
      <category>개발/프로젝트</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/582</guid>
      <comments>https://kwonputer.tistory.com/582#entry582comment</comments>
      <pubDate>Sun, 16 Mar 2025 21:43:45 +0900</pubDate>
    </item>
    <item>
      <title>[AI] Llama 인공지능 시작하기: Windows 환경</title>
      <link>https://kwonputer.tistory.com/581</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;안녕하세요!&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;오늘도 블로그를 찾아주셔서 감사합니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;이번 글에서는 라마3(Llama3)를 체험해 보려고 합니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;GUI 방식을 사용하는 LM Studio &amp;amp; 명령어 프롬프트(CMD) 방식을 사용하는 Ollama 두 가지 모두를 해보려고 합니다.&amp;nbsp;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;함께 체험해 보시죠~!&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;※ Llama3의 주요 특징&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 8B, 70B 사이즈의 모델이 출시되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● &amp;nbsp;기본 모델과 대화용 모델(Chat/Instruct) 버전이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● &amp;nbsp;Meta의 AI 연구 시설인 FAIR(Facebook AI Research)에서 개발했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목차&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ Ollama (CMD) 시작하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. Ollama 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. Llama3 모델 다운로드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 모델 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ LM Studio (GUI) 시작하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. LM Studio 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. Llama3 모델 다운로드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 모델 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;◎ Ollama (CMD) 시작하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. Ollama 설치&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;a href=&quot;https://ollama.com/download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Ollama 공식 웹사이트&lt;/a&gt;에서 Windows 버전을 다운로드합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmBl3d/btsMHQ9XV7t/ht2VBkv0fKVqfyvh7fhyh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmBl3d/btsMHQ9XV7t/ht2VBkv0fKVqfyvh7fhyh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmBl3d/btsMHQ9XV7t/ht2VBkv0fKVqfyvh7fhyh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmBl3d%2FbtsMHQ9XV7t%2Fht2VBkv0fKVqfyvh7fhyh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;721&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjOEgi/btsMGdrzhFH/uTFNY3TXXFlXKk3UWrjVq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjOEgi/btsMGdrzhFH/uTFNY3TXXFlXKk3UWrjVq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjOEgi/btsMGdrzhFH/uTFNY3TXXFlXKk3UWrjVq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjOEgi%2FbtsMGdrzhFH%2FuTFNY3TXXFlXKk3UWrjVq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;894&quot; height=&quot;663&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. Llama3 모델 다운로드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 명령 프롬프트(CMD)를 실행하고 다음 명령어를 입력해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741661607774&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama pull llama3:8b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmqARk/btsMFrjKZhM/zTk9a8qrEqpEoAenZkaKN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmqARk/btsMFrjKZhM/zTk9a8qrEqpEoAenZkaKN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmqARk/btsMFrjKZhM/zTk9a8qrEqpEoAenZkaKN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmqARk%2FbtsMFrjKZhM%2FzTk9a8qrEqpEoAenZkaKN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;977&quot; height=&quot;514&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 모델 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 명령 프롬프트(CMD)에서 다음 명령어로 대화형 모드를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741661767319&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama run llama3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mm3my/btsMFljx4cE/DNQbrrpK3oUKrgtFYP8TL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mm3my/btsMFljx4cE/DNQbrrpK3oUKrgtFYP8TL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mm3my/btsMFljx4cE/DNQbrrpK3oUKrgtFYP8TL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMm3my%2FbtsMFljx4cE%2FDNQbrrpK3oUKrgtFYP8TL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;511&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;◎ LM Studio (GUI) 시작하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. LM Studio 설치&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● &lt;a href=&quot;https://lmstudio.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LM Studio Github&lt;/a&gt; 페이지에서 Windows 설치 파일을 다운로드합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTRYEE/btsMHauiEiu/GsPkDVdbbWZigcFKwlex1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTRYEE/btsMHauiEiu/GsPkDVdbbWZigcFKwlex1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTRYEE/btsMHauiEiu/GsPkDVdbbWZigcFKwlex1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTRYEE%2FbtsMHauiEiu%2FGsPkDVdbbWZigcFKwlex1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1167&quot; height=&quot;599&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JtTTH/btsMHxpcHDJ/kxJwmNxkFQEDys9g7rHBTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JtTTH/btsMHxpcHDJ/kxJwmNxkFQEDys9g7rHBTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JtTTH/btsMHxpcHDJ/kxJwmNxkFQEDys9g7rHBTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJtTTH%2FbtsMHxpcHDJ%2FkxJwmNxkFQEDys9g7rHBTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;370&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. Llama3 모델 다운로드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● LM Studio를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 왼쪽 패널에서 돋보기 모양의 &quot;Discover&quot; 탭을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 검색창에 &quot;Llama 3&quot;를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 목록에서 원하는 Llama3 모델(Meta&amp;nbsp;Llama&amp;nbsp;3.1&amp;nbsp;8B)을 찾아 다운로드 버튼을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;951&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTFq3z/btsME8ECAmf/dy1pY5h8NXDEk86FNMkLE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTFq3z/btsME8ECAmf/dy1pY5h8NXDEk86FNMkLE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTFq3z/btsME8ECAmf/dy1pY5h8NXDEk86FNMkLE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTFq3z%2FbtsME8ECAmf%2Fdy1pY5h8NXDEk86FNMkLE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1650&quot; height=&quot;951&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;951&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E8IVD/btsMHnG6Ydp/62MlJZ0yjnJtf6ZbR8GYm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E8IVD/btsMHnG6Ydp/62MlJZ0yjnJtf6ZbR8GYm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E8IVD/btsMHnG6Ydp/62MlJZ0yjnJtf6ZbR8GYm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE8IVD%2FbtsMHnG6Ydp%2F62MlJZ0yjnJtf6ZbR8GYm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;304&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ycAaF/btsMFyJGj12/XFpxRTH6Rbb0I8jxbCLRu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ycAaF/btsMFyJGj12/XFpxRTH6Rbb0I8jxbCLRu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ycAaF/btsMFyJGj12/XFpxRTH6Rbb0I8jxbCLRu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FycAaF%2FbtsMFyJGj12%2FXFpxRTH6Rbb0I8jxbCLRu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;798&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 모델 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 다운로드가 완료되면 모델을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &quot;Chat&quot; 탭에서 모델과 대화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJMTYe/btsMHSfEorE/qcQH2ElEKVAdqZF8jtetDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJMTYe/btsMHSfEorE/qcQH2ElEKVAdqZF8jtetDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJMTYe/btsMHSfEorE/qcQH2ElEKVAdqZF8jtetDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJMTYe%2FbtsMHSfEorE%2FqcQH2ElEKVAdqZF8jtetDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;359&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;949&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYNqXW/btsMGNzhV9O/hIP9kNaKc6Hom6kK2jsoKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYNqXW/btsMGNzhV9O/hIP9kNaKc6Hom6kK2jsoKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYNqXW/btsMGNzhV9O/hIP9kNaKc6Hom6kK2jsoKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYNqXW%2FbtsMGNzhV9O%2FhIP9kNaKc6Hom6kK2jsoKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1648&quot; height=&quot;949&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;949&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 Ollama를 사용하기 위해서는 Mac 사용이 필수였던 것으로 기억이 납니다. 이제는 Windows 환경에서도 쉽게 접근할 수 있다는 게 기술의 발전 속도를 보여주는 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 Python &amp;amp; Django를 사용해서 Llama3 모델을 활용하는 방법에 대해 다뤄보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 다음 포스트에서 만나요~!&lt;/p&gt;</description>
      <category>개발/AI &amp;middot; 머신러닝</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/581</guid>
      <comments>https://kwonputer.tistory.com/581#entry581comment</comments>
      <pubDate>Tue, 11 Mar 2025 11:47:13 +0900</pubDate>
    </item>
    <item>
      <title>2025 한국과 글로벌 교육 시장 완전 분석: 언어 교육과 코딩 교육의 미래</title>
      <link>https://kwonputer.tistory.com/578</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;안녕하세요!&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;오늘도 블로그를 찾아주셔서 감사합니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;이번 글에서는 2023~2024년 기준으로 한국과 글로벌 교육 시장을 분석하고, 특히 언어 교육과 코딩 교육 분야의 현황과 미래를 살펴보려고 합니다. 디지털 전환과 AI 기술의 발전으로 교육 시장은 빠르게 변화하고 있으며, 이러한 변화는 교육자와 학습자 모두에게 새로운 기회와 도전을 제공하고 있습니다. 데이터를 기반으로 한 인사이트를 통해 교육 시장의 흐름을 함께 파악해 보시죠!&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;◎ 요약&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 본 글은 2023~2024년 기준 한국과 글로벌 교육 시장을 분석합니다. 한국 교육 시장은 약 25.9조 원 규모이며, 사교육 참여율 78.5%, 에듀테크 시장은 5조 원에 달합니다. 글로벌 교육 시장은 7조 달러 규모로, 언어 교육(978억 달러)과 코딩 교육(26.5억 달러) 분야가 각각 연평균 17.67%와 22.50%의 높은 성장률을 보입니다. 한국은 사교육과 시험 중심, 글로벌은 실용적 역량 개발에 초점을 맞추는 차이가 있습니다. 미래에는 AI 기반 개인화 학습 &amp;amp; 메타버스 활용 교육이 주요 트렌드가 될 것이며, 평생 학습의 중요성이 더욱 커질 전망입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목차&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 한국 교육 시장 분석&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. 한국 교육 시장 개요&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. 한국 사교육 시장 현황&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 한국 에듀테크 시장 동향&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;4. 언어 교육 시장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;5. 코딩 교육 시장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 글로벌 교육 시장 분석&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. 글로벌 교육 시장 개요&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. 주요국 교육 시장 동향&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 글로벌 언어 교육 시장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;4. 글로벌 코딩 교육 시장&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 한국과 글로벌 교육 시장 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. 시장 구조 및 규모 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. 에듀테크 도입 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 언어 교육 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;4. 코딩 교육 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 교육 시장 미래 예측&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. 교육 시장 성장 전망&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. 교육 기술 혁신 전망&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 언어 교육의 미래&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;4. 코딩 교육의 미래&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;5. 평생 학습과 직업 교육의 미래&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 결론&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #456771;&quot;&gt;◎ 한국 교육 시장 분석&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. 한국 교육 시장 개요&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 교육 시장은 전통적으로 사교육 중심의 강한 성장세를 보여왔으나, 최근에는 에듀테크와 디지털 전환의 영향으로 시장 구조가 크게 변화하고 있습니다. 특히 코로나19 이후, 비대면 교육 수요가 증가하면서 온라인 교육 플랫폼의 성장이 두드러지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 교육 시장은 2022년 기준으로 약 25.9조원 규모로 추정됩니다. 특히 주목할 점은 디지털 기반 교육 시장의 성장세로, 연평균 10% 이상의 성장률을 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. 한국 사교육 시장 현황&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;b&gt;&lt;span style=&quot;color: #009a87; font-family: 'Noto Serif KR';&quot;&gt;한국의 사교육 시장은 여전히 강세를 보이고 있으며, 통계청 자료에 따르면 2023년 기준 초중고 학생의 사교육 참여율은 78.5%로 나타났습니다.&lt;/span&gt;&lt;/b&gt; 사교육비 총액은 약 27.1조 원으로 집계되었으며, 학생 1인당 월평균 사교육비는 43만 원을 상회하는 것으로 조사되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 특히 영어와 수학 분야의 사교육비 지출이 가장 큰 비중을 차지하고 있으며, 이는 토익 스피킹을 포함한 영어 교육 시장의 지속적인 성장 가능성을 시사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 한국 에듀테크 시장 동향&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 에듀테크 시장은 급격한 성장세를 보이고 있으며, AI &amp;amp; VR/AR &amp;amp; 빅데이터... 등 신기술을 접목한 교육 서비스가 늘어나고 있습니다. &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2024년 한국 에듀테크 시장 규모는 약 5조 원으로 추정되며, 이는 전체 교육 시장의 중요한 부분을 차지하고 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 주목할 만한 트렌드로는 개인 맞춤형 학습 솔루션 &amp;amp; 메타버스 기반 교육 &amp;amp; 코딩 교육 플랫폼... 등이 있으며, 특히 코딩 교육 분야는 소프트웨어 교육 의무화 정책에 힘입어 지속적인 성장을 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;4. 언어 교육 시장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 영어 교육 시장은 여전히 교육 분야에서 큰 비중을 차지하고 있으며, 특히 취업과 직접적으로 연관된 토익 &amp;amp; 토익 스피킹... 등의 시험 대비 교육이 주요 시장을 형성하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #009a87;&quot;&gt;&lt;b&gt;언어 교육 시장의 주요 특징으로는 온라인 학습 플랫폼의 성장 &amp;amp; AI 기반 연습 도구의 등장 &amp;amp; 1:1 맞춤형 코칭 서비스의 인기가 있습니다.&lt;/b&gt;&lt;/span&gt; 특히 취업 준비생들 사이에서 실용적인 영어 말하기 능력의 중요성이 강조되면서, 스피킹 교육 수요는 지속적으로 증가하는 추세입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. 코딩 교육 시장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #009a87; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;한국의 코딩 교육 시장은 2021년 초중고 소프트웨어 교육 의무화 이후 급격한 성장을 보입니다.&lt;/b&gt;&lt;/span&gt; 코딩 교육 스타트업인 팀스파르타 &amp;amp; 코드잇 &amp;amp; 코드스테이츠가 꾸준히 상승세를 이루고 있습니다. 이는, 코딩 교육 시장이 한국 교육 시장의 중요한 부분을 차지할 것을 시사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 특히 주목할 점은 아동 대상 코딩 교육부터 성인 대상 재교육까지 전 연령대를 아우르는 시장이 형성되고 있다는 것입니다. 이는 디지털 전환 시대에 맞춘 실용적 기술 교육의 중요성을 반영합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;◇ 참고(Reference)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://keris.or.kr/main/na/ntt/selectNttInfo.do?mi=1244&amp;amp;nttSn=40506&amp;amp;bbsId=1104#none&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;한국교육학술정보원 &quot;2023 디지털 교육백서&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://kostat.go.kr/board.es?mid=a10301010000&amp;amp;bid=245&amp;amp;list_no=429923&amp;amp;act=view&amp;amp;mainXml=Y&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;통계청 &quot;2023년 초중고사교육비조사 결과&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.nipa.kr/home/2-7-1-1/15512&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;정보통신산업진흥원 &quot;2023년 이러닝 산업실태 보고서&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://toeicstory.tistory.com/2457&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;한국토익위원회 &quot;2024년 토익스피킹 정기시험 성적 분석&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.innoforest.co.kr/report/NS00000177?utm_source=brunch&amp;amp;utm_medium=social&amp;amp;utm_campaign=editor-10&amp;amp;utm_term=blog&amp;amp;utm_content=NS00000177&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;혁신의숲 Jay &quot;코딩교육 스타트업 성장 분석 (팀스파르타, 코드잇, 코드스테이츠)&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #456771;&quot;&gt;◎ 글로벌 교육 시장 분석&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. 글로벌 교육 시장 개요&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #009a87;&quot;&gt;&lt;b&gt;글로벌 교육 시장은 2024년 기준 약 7조 달러 규모로 추정되며, 이는 전 세계 GDP의 약 6%에 해당하는 규모입니다.&lt;/b&gt;&lt;/span&gt; 특히 에듀테크 분야는 연평균 15% 이상의 성장률을 보이며, 2030년까지 약 4,040억 달러 규모로 성장할 것으로 예상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 글로벌 교육 시장의 주요 트렌드로는 디지털 학습 플랫폼의 확산 &amp;amp; AI 기반 개인화 학습 &amp;amp; 평생 학습 시장의 성장... 등이 있으며, 코로나19 이후 하이브리드 학습 모델이 새로운 표준으로 자리 잡고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. 주요국 교육 시장 동향&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;※ 북미 지역&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 북미 교육 시장은 에듀테크 투자가 가장 활발한 지역입니다. 특히 미국은 K-12 교육 &amp;amp; 고등 교육 &amp;amp; 기업 교육... 등 전 분야에서 혁신적인 교육 모델이 발전하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;※ 아시아 태평양 지역&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 아시아 태평양 지역은 가장 빠르게 성장하는 교육 시장으로, 중국과 인도가 주도하고 있습니다. 특히 중국의 교육 시장은 온라인 교육과 영어 학습 분야에서 높은 성장세를 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;※ 유럽 지역&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 유럽의 교육 시장은 디지털 역량 개발과 평생 학습 프로그램에 중점을 두고 있습니다. 특히 독일 &amp;amp; 프랑스 &amp;amp; 영국... 등에서는 정부 주도의 디지털 교육 이니셔티브가 활발히 진행되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 글로벌 언어 교육 시장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #009a87;&quot;&gt;&lt;b&gt;Business Research Insights의 언어 학습 시장 보고서에 따르면, 글로벌 언어 교육 시장은 2024년 978억 달러에 달했으며 2033년까지 4,200억 달러에 달할 것으로 예상&lt;/b&gt;&lt;/span&gt;되며, 2033년까지 연평균 17.67% 성장률이 예상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #009a87;&quot;&gt;&lt;b&gt;Duolingo의 2024년 언어 학습 트렌드 보고서에 따르면, 온라인 언어 학습 플랫폼 사용자는 전년 대비 25% 증가&lt;/b&gt;&lt;/span&gt;했으며, 특히 AI 음성 인식 기술을 활용한 발음 평가 기능이 인기를 끌고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 주목할 만한 트렌드로는 AI 기반 언어 학습 앱의 성장 &amp;amp; 실시간 원어민 화상 수업 서비스의 확대 &amp;amp; 기업 대상 비즈니스 영어 교육의 증가가 있습니다. 특히 토익 스피킹을 포함한 실용적인 비즈니스 영어 능력 검증 시험의 중요성이 글로벌 시장에서도 증가하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;4. 글로벌 코딩 교육 시장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #009a87;&quot;&gt;&lt;b&gt;Research and Markets에 따르면, 코딩 부트캠프 시장은 2023년 21억 7,000만 달러에서 2024년 26억 5,000만 달러로 성장했습니다.&lt;/b&gt;&lt;/span&gt; 연평균 22.50%의 성장률을 이어가며 2030년에는 89억 9,000만 달러에 달할 것으로 예상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 글로벌 코딩 교육 시장의 주요 트렌드로는 부트캠프 형태의 단기 집중 교육 &amp;amp; 구독 기반 온라인 학습 플랫폼의 인기 상승이 있습니다. 특히 인공지능 &amp;amp; 데이터 사이언스 &amp;amp; 클라우드 컴퓨팅 분야의 코딩 교육이 높은 성장세를 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;◇ 참고(Reference)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.grandviewresearch.com/industry-analysis/education-technology-market&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Grand&amp;nbsp;View&amp;nbsp;Research,&amp;nbsp;&quot;Education&amp;nbsp;Technology&amp;nbsp;Market&amp;nbsp;Size&amp;nbsp;Report,&amp;nbsp;2024-2030&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.brighteyevc.com/post/the-european-edtech-funding-report-2025&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Brighteye Ventures &quot;The European Edtech Funding Report 2025&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://education.ec.europa.eu/focus-topics/digital-education/action-plan&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;European Commission &quot;Digital Education Action Plan (2021-2027)&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://blog.duolingo.com/2024-duolingo-language-report/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Duolingo &quot;2024 duolingo language report&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.researchandmarkets.com/report/coding-bootcamp?srsltid=AfmBOooW7xJOfW4SrotWd-cTC1ZHkNUYpnSKUkC0m7Xwth0Avp0zrycp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Research and Markets &quot;Coding Bootcamp Market by Mode of Delivery, End-User - Global Forecast 2025-2030&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.businessresearchinsights.com/ko/market-reports/language-learning-market-118229&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Business Research Insights &quot;Language Learning Market Size, Share, Growth and Industry Analysis, By Type (English, Spanish, Chinese, French, German, Japanese, Korean), Applications (Direct Personal Map, Digital Map) and Regional Insights and 2033 Forecast.&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;◎ 한국과 글로벌 교육 시장 비교&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. 시장 구조 및 규모 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 교육 시장은 사교육 중심의 구조로 되어 있으며, GDP 대비 교육 지출 비중이 OECD 국가 중 상위권에 속합니다. 반면, 글로벌 교육 시장은 국가별로 다양한 구조를 보이지만, 전반적으로 공교육 중심의 구조로 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국은 학령인구 감소에도 불구하고 1인당 교육 지출이 증가하는 추세이며, 특히 영어와 코딩 교육에 대한 투자가 지속적으로 증가하고 있습니다. 이는 글로벌 시장의 트렌드와 유사하지만, 한국은 특히 사교육 시장의 비중이 더 높다는 특징이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. 에듀테크 도입 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 에듀테크 도입 속도는 글로벌 평균보다 빠른 편이며, 특히 고속 인터넷 인프라와 높은 스마트 기기 보급률을 바탕으로 온라인 교육 플랫폼의 활용도가 높습니다. 그러나 교육과정의 혁신 측면에서는 글로벌 선진국에 비해 다소 보수적인 경향을 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 글로벌 에듀테크 시장에서는 AI 기반 개인화 학습 &amp;amp; 게이미피케이션 &amp;amp; 메타버스 활용... 등의 혁신이 활발히 이루어지고 있으며, 한국도 이러한 트렌드를 적극적으로 수용하고 있으나, 입시 중심의 교육 환경이 혁신의 제약 요인으로 작용하기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 언어 교육 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #009a87; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;한국의 영어 교육 시장은 시험 대비(토익 &amp;amp; 토플... 등) 중심의 구조로 되어 있으며, 실용적인 의사소통 능력보다 시험 점수 향상에 중점을 두는 경향이 있습니다.&lt;/b&gt;&lt;/span&gt; 반면, 글로벌 언어 교육 시장은 실용적인 의사소통 능력과 문화적 이해를 강조하는 추세입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 최근 한국의 토익 스피킹 교육 시장은 글로벌 트렌드에 맞추어 실용적인 의사소통 능력을 강조하는 방향으로 변화하고 있으며, AI 음성 인식 기술을 활용한 학습 도구의 도입이 증가하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;4. 코딩 교육 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한국의 코딩 교육은 2021년부터 초중고 교육과정에 의무화되었으며, 이는 글로벌 트렌드와 일치합니다. 그러나 교육 내용 측면에서는 기초적인 알고리즘 이해에 중점을 두는 경향이 있으며, 실무 지향적인 프로젝트 기반 학습은 상대적으로 부족한 상황입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 글로벌 코딩 교육 시장에서는 프로젝트 기반 학습 &amp;amp; 협업 도구 활용 &amp;amp; 실제 산업 문제 해결... 등 실무 지향적인 접근법이 강조되고 있으며, 한국도 점차 이러한 방향으로 변화하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;◇ 참고(Reference) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.oecd.org/en/publications/education-at-a-glance-2024_c00cad36-en.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OECD &quot;Education at a Glance 2024&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.kait.or.kr/user/boardDetail.do?cateSeq=3&amp;amp;bId=109&amp;amp;bSeq=9426&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;한국정보통신진흥협회 &quot;2023 ICT실태조사 보고서&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://mktgfiles.britishcouncil.org/hubfs/FoE_Research%20Summary_single%20page_for%20download_revisedV2.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;British Council &quot;The Future of English: Global Perspectives&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #456771;&quot;&gt;◎ 교육 시장 미래 예측&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. 교육 시장 성장 전망&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 글로벌 교육 시장은 2030년까지 연평균 5% 이상의 성장률을 보일 것으로 전망되며, 특히 에듀테크 분야는 15% 이상의 고성장이 예상됩니다. 한국의 교육 시장도 전체적으로는 완만한 성장을 보이겠지만, 에듀테크와 평생 학습 분야에서는 높은 성장세가 예상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 특히 주목할 점은 학령인구 감소에도 불구하고 1인당 교육 투자 금액은 증가할 것으로 예상된다는 것입니다. 이는 고품질 교육에 대한 수요 증가와 평생 학습의 중요성 강화를 반영합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. 교육 기술 혁신 전망&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 향후 5년간 교육 기술 분야에서는 AI 기반 개인화 학습 &amp;amp; 메타버스를 활용한 몰입형 교육 &amp;amp; 마이크로 러닝... 등이 주요 트렌드로 자리 잡을 것으로 예상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 특히 AI는 교육의 모든 영역에 영향을 미칠 것으로 예상되며, 학습자 분석 &amp;amp; 맞춤형 콘텐츠 제공 &amp;amp; 자동화된 피드백 &amp;amp; 행정 업무 효율화... 등 다양한 분야에서 활용될 것입니다. 또한 블록체인 기술을 활용한 교육 이력 관리 및 인증 시스템도 확산할 전망입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 언어 교육의 미래&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 언어 교육 분야에서는 AI 기반 음성 인식 및 자연어 처리 기술의 발전으로 실시간 피드백이 가능한 개인화된 학습 경험이 더욱 보편화될 것으로 예상됩니다. 특히 메타버스 환경에서의 몰입형 언어 학습이 새로운 트렌드로 부상할 전망입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 토익 스피킹을 포함한 언어 능력 평가 방식도 변화할 것으로 예상되며, 단순 점수 측정보다 실제 의사소통 능력과 문화적 이해도를 종합적으로 평가하는 방향으로 발전할 것입니다. 또한 글로벌 비즈니스 환경에서 요구되는 특수 목적 영어(ESP) 교육의 중요성이 더욱 커질 전망입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;4. 코딩 교육의 미래&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 코딩 교육은 단순한 프로그래밍 기술 습득을 넘어 컴퓨팅 사고력과 문제 해결 능력 개발로 그 초점이 확장될 것으로 예상됩니다. 또한 로우코드/노코드 플랫폼의 발전으로 코딩 교육의 진입 장벽이 낮아지고, 더 많은 사람들이 소프트웨어 개발에 참여할 수 있게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;5. 평생 학습과 직업 교육의 미래&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 기술의 빠른 발전과 직업 환경의 변화로 인해 평생 학습과 재교육의 중요성이 더욱 커질 것으로 예상됩니다. &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #009a87;&quot;&gt;&lt;b&gt;World Economic Forum의 &quot;The Future of Jobs Report 2025&quot;에 따르면, 2030년까지 전 세계 직업의 대부분이 AI와 자동화로 인해 변화할 것으로 예측&lt;/b&gt;&lt;/span&gt;되며, 이에 따라 대부분의 근로자가 재교육이 필요할 것으로 전망됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 기업 주도의 교육 투자도 확대될 것으로 예상되며, 직원 역량 개발을 위한 맞춤형 교육 프로그램과 평가 시스템이 발전할 전망입니다. 또한 마이크로 자격증과 같은 유연한 인증 시스템이 전통적인 학위를 보완하는 형태로 확산될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;◇ 참고(Reference)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://unesdoc.unesco.org/ark:/48223/pf0000385723&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Unesdoc &quot;Global education monitoring report, 2023: technology in education: a tool on whose terms?&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://survey.stackoverflow.co/2024/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Stack Overflow &quot;2024 Developer Survey&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;● &lt;a href=&quot;https://www.weforum.org/publications/the-future-of-jobs-report-2025/?gad_source=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Economic Forum &quot;The&amp;nbsp;Future&amp;nbsp;of&amp;nbsp;Jobs&amp;nbsp;Report&amp;nbsp;2025&quot;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;◎ 결론&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;교육 시장은 기술 발전과 사회 변화에 따라 지속적으로 진화하고 있으며, 특히 디지털 전환과 AI의 발전은 교육의 방식과 내용을 근본적으로 변화시키고 있습니다. 한국의 교육 시장은 글로벌 트렌드를 적극적으로 수용하면서도, 입시 중심의 교육 문화와 사교육 의존도가 높다는 독특한 특성이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;토익 스피킹 교육과 코딩 교육은 모두 미래 직업 시장에서 요구되는 핵심 역량과 직결되는 분야로, 앞으로도 지속적인 성장이 예상됩니다. 특히 AI 기반 개인화 학습 &amp;amp; 메타버스 활용 몰입형 교육 &amp;amp; 프로젝트 기반 실무 중심 교육... 등의 혁신적 접근법이 이 분야의 미래를 주도할 것으로 전망됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;교육 시장에서 성공하기 위해서는 기술 발전을 적극적으로 수용하면서도, 학습자의 실질적인 역량 개발과 성장을 지원하는 교육 철학을 견지하는 것이 중요할 것입니다. 또한 글로벌 트렌드와 한국 시장의 특수성을 모두 고려한 균형 잡힌 접근이 필요할 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;긴&amp;nbsp;글을&amp;nbsp;읽어주셔서&amp;nbsp;감사합니다! &lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;처음 교육 시장을 분석하기 시작했을 때는 단순히 통계 자료들의 나열에 불과할 것으로 생각했습니다. 하지만 데이터를 깊이 파고들수록, 한국과 글로벌 교육 시장의 역동적인 변화와 미래 가능성을 발견하게 되었습니다. &lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;특히&amp;nbsp;언어&amp;nbsp;교육과&amp;nbsp;코딩&amp;nbsp;교육이&amp;nbsp;AI&amp;nbsp;기술과&amp;nbsp;결합하면서&amp;nbsp;어떻게&amp;nbsp;변화하고&amp;nbsp;있는지,&amp;nbsp;그리고&amp;nbsp;이러한&amp;nbsp;변화가&amp;nbsp;우리의&amp;nbsp;학습&amp;nbsp;방식과&amp;nbsp;직업&amp;nbsp;환경에&amp;nbsp;어떤&amp;nbsp;영향을&amp;nbsp;미칠지&amp;nbsp;고민해&amp;nbsp;보는&amp;nbsp;시간이었습니다.&amp;nbsp;제가&amp;nbsp;이&amp;nbsp;글을&amp;nbsp;통해&amp;nbsp;전하고&amp;nbsp;싶은&amp;nbsp;메시지는&amp;nbsp;단순합니다.&amp;nbsp;교육은&amp;nbsp;이제&amp;nbsp;단순한&amp;nbsp;지식&amp;nbsp;전달을&amp;nbsp;넘어,&amp;nbsp;기술과&amp;nbsp;함께&amp;nbsp;진화하며&amp;nbsp;개인의&amp;nbsp;성장과&amp;nbsp;사회&amp;nbsp;발전의&amp;nbsp;핵심&amp;nbsp;동력이&amp;nbsp;될&amp;nbsp;것이라는&amp;nbsp;점입니다. &lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;저&amp;nbsp;역시&amp;nbsp;개발자이자&amp;nbsp;평생&amp;nbsp;학습자로서&amp;nbsp;이러한&amp;nbsp;변화와&amp;nbsp;함께&amp;nbsp;성장하고&amp;nbsp;있으며,&amp;nbsp;앞으로도&amp;nbsp;교육과&amp;nbsp;기술의&amp;nbsp;융합에&amp;nbsp;대한&amp;nbsp;인사이트를&amp;nbsp;나누고&amp;nbsp;싶습니다.&amp;nbsp;여러분의&amp;nbsp;학습과&amp;nbsp;성장&amp;nbsp;여정에&amp;nbsp;이&amp;nbsp;글이&amp;nbsp;작은&amp;nbsp;도움이&amp;nbsp;되었기를&amp;nbsp;희망합니다. &lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;감사합니다!&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT &amp;middot; 테크/기술 트렌드</category>
      <category>AI 교육</category>
      <category>교육 시장 분석</category>
      <category>디지털 교육</category>
      <category>메타버스 교육</category>
      <category>미래 교육 시장 예측</category>
      <category>사교육 시장</category>
      <category>에듀테크</category>
      <category>온라인 학습</category>
      <category>코딩 교육</category>
      <category>토익 스피킹</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/578</guid>
      <comments>https://kwonputer.tistory.com/578#entry578comment</comments>
      <pubDate>Sun, 9 Mar 2025 22:24:04 +0900</pubDate>
    </item>
    <item>
      <title>Google의 AI 비전과 Flutter의 만남: 인공지능 시대의 크로스플랫폼 선택법</title>
      <link>https://kwonputer.tistory.com/577</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;안녕하세요!&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;오늘도 블로그를 찾아주셔서 감사합니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;이번 글에서는 제가 깊이 관심을 두고 있는 Flutter와 AI의 결합 가능성에 관해 이야기해보려 합니다.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;크로스플랫폼 개발과 인공지능 기술의 융합 트렌드를 분석해 보면, 여러 지표와 시장 동향이 Flutter의 성장 가능성을 시사하고 있습니다. 데이터 기반 접근으로 이러한 기술 발전 추세를 살펴보겠습니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;◎ 요약&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 본 글은 Google의 Flutter와 AI 기술의 융합 가능성을 분석합니다. 스택 오버플로의 개발자 서베이 결과는 Flutter가 크로스플랫폼 시장에서 React Native와 같은 경쟁자들과 나란히 성장하고 있음을 보여줍니다. Flutter의 주요 강점은 Google의 AI 생태계(TensorFlow &amp;amp; ML Kit &amp;amp; Gemini API)와의 긴밀한 통합, 단일 코드베이스로 다양한 플랫폼 지원, 그리고 풍부한 AI 라이브러리 생태계입니다. Google Pay &amp;amp; 알리바바... 등 글로벌 기업들의 성공 사례는 Flutter의 확장성과 안정성을 증명합니다. 앞으로 Flutter는 Gemini API 지원 강화 &amp;amp; TensorFlow Lite 성능 최적화 &amp;amp; AR 지원 확대 등을 통해 AI 시대의 필수적인 개발 도구로 자리매김할 것입니다. 개발자로서 Flutter와 Google의 AI 기술을 함께 학습하는 것은 미래를 준비하는 현명한 투자가 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목차&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ Flutter 성장 지표 분석&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 인공지능 시대의 기술 생태계 변화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ Flutter와 AI 통합의 우위성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. Google의 AI 생태계와의 긴밀한 통합&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. 단일 코드베이스로 다양한 플랫폼 지원&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. 성장하는 AI 라이브러리 생태계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 실제 사례로 보는 Flutter와 AI의 결합&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. Google Pay(인도)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. eBay Motors&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. My BMW&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;4. Reflectly&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;5. Hamilton&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;6. 알리바바&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ Flutter와 Google AI의 미래 전망&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;1. Gemini API의 Flutter 공식 지원&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;2. TensorFlow Lite 및 ML Kit의 진화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;3. Flutter 3.X의 AR 지원 강화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;4. Firebase와 ML 서비스의 통합 강화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;5. 엔터프라이즈 솔루션을 위한 성능 최적화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #666666;&quot;&gt;◎ 결론&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;◎ Flutter 성장 지표 분석&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;└ 스택 오버플로의 개발자 서베이 결과는 Flutter가 크로스플랫폼 시장에서 보이는 성장세를 객관적으로 보여줍니다. 2019년부터 2024년까지의 데이터를 비교해 보면, Flutter가 React Native와 같은 주요 경쟁자들과 어깨를 나란히 하는 추세가 확인됩니다. 이는 개발자 커뮤니티 내에서 Flutter의 인지도와 채택률이 꾸준히 상승하고 있음을 나타냅니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;※ 2019 ~ 2024년 스택 오버플로 인기 순위&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;1190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/viia0/btsMAZaeDOQ/6sB7ItofYtnKlGj1WDSBKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/viia0/btsMAZaeDOQ/6sB7ItofYtnKlGj1WDSBKk/img.png&quot; data-alt=&quot;2019년 스택 오버플로 인기 순위&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/viia0/btsMAZaeDOQ/6sB7ItofYtnKlGj1WDSBKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fviia0%2FbtsMAZaeDOQ%2F6sB7ItofYtnKlGj1WDSBKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1250&quot; height=&quot;1190&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;1190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2019년 스택 오버플로 인기 순위&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2400&quot; data-origin-height=&quot;4170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C9K8s/btsMCAmppKb/ehlgZ4uiYnl4oWz7ehnVv1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C9K8s/btsMCAmppKb/ehlgZ4uiYnl4oWz7ehnVv1/img.webp&quot; data-alt=&quot;2024년 스택 오버플로 인기 순위&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C9K8s/btsMCAmppKb/ehlgZ4uiYnl4oWz7ehnVv1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC9K8s%2FbtsMCAmppKb%2FehlgZ4uiYnl4oWz7ehnVv1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2400&quot; height=&quot;4170&quot; data-origin-width=&quot;2400&quot; data-origin-height=&quot;4170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2024년 스택 오버플로 인기 순위&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;◎ 인공지능 시대의 기술 생태계 변화&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://eopla.net/magazines/25012#&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* 트럼프 2.0시대의 2025년 AI 트렌드 10가지&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;최근 기술 분석 리포트들은 AI가 단순한 기술 도구를 넘어 국가 경쟁력의 핵심 요소로 부상하고 있음을 강조합니다. 특히 데이터센터와 에너지 인프라가 전략적 자원으로 재평가되는 상황에서, 크로스플랫폼 기술의 중요성은 더욱 커질 것으로 전망됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Google이 TensorFlow와 같은 머신러닝 프레임워크를 통해 AI 생태계에서 보여준 영향력을 고려할 때, &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Google이 지원하는 Flutter의 발전 가능성에 주목&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;할 필요가 있습니다. 특히 Gemini를 필두로 한 생성형 AI 기술이 모바일 애플리케이션과 결합하는 추세는, &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Flutter가 앞으로 크로스플랫폼 개발의 주요 선택지로 자리잡을 가능성&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;을 시사합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://firebase.google.com/docs/ml/flutter/use-custom-models?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Flutter와 함께 커스텀 TensorFlow Lite 모델 사용&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;머신러닝 모델을 쉽게 통합할 수 있는 TensorFlow Lite의 Flutter 지원은 이미 개발자들에게 AI 기능 구현의 진입 장벽을 낮추고 있습니다. 이러한 기술적 이점이 산업 전반으로 확산하면서, &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;미래 모바일 시장은 AI와 VR이 통합된 애플리케이션이 주도할 것으로 예측&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;◎ Flutter와 AI 통합의 우위성&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;└ Flutter가 인공지능 시대에 모바일 크로스플랫폼 개발의 선두 주자가 될 수밖에 없는 이유는, 몇 가지 핵심적인 강점에 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;1. Google의 AI 생태계와의 긴밀한 통합&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://pub.dev/packages/google_generative_ai&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Google의 Gemini 라이브러리 공식 지원&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;● &lt;/span&gt;Google은 AI 기술 분야의 선두 주자로, Flutter는 이러한 Google의 AI 생태계와 원활하게 통합됩니다. TensorFlow Lite &amp;amp; ML Kit 그리고 최근의 Gemini API까지, &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Flutter 개발자는 Google의 강력한 AI 도구들을 쉽게 활용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt; 이는 단순히 기술적 편의성을 넘어 전략적 우위를 제공합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 단일 코드베이스로 다양한 플랫폼 지원&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;● Flutter의 가장 큰 장점 중 하나는 &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;단일 코드베이스로 AOS &amp;amp; IOS &amp;amp; 웹 &amp;amp; 데스크톱... 등 다양한 플랫폼을 지원&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;한다는 점입니다. AI 기능이 통합된 앱을 개발할 때, 이러한 크로스플랫폼 능력은 개발 시간과 비용을 크게 절감해 줍니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333;&quot;&gt;3. 성장하는 AI 라이브러리 생태계&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;● &lt;/span&gt;Flutter는 이미 다양한 AI 관련 라이브러리들이 생태계를 형성하고 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://pub.dev/packages/tflite_flutter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* flutter_tflite: TensorFlow Lite 모델을 쉽게 통합&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://pub.dev/packages/google_ml_kit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* google_mlkit: 이미지 인식 &amp;amp; 텍스트 인식... 등 다양한 ML 기능 지원&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://pub.dev/packages/google_generative_ai&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* flutter_gemini: Google의 최신 생성형 AI 통합&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #456771;&quot;&gt;&lt;b&gt;◎ 실제 사례로 보는 Flutter와 AI의 결합&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;└ 이미 여러 글로벌 기업들이 Flutter와 AI를 결합한 혁신적인 애플리케이션을 출시하고 있습니다. 이러한 앱들의 성공 사례는 Flutter가 AI 시대의 선두 주자가 될 수 있음을 실질적으로 증명합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;1. &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Google Pay(인도)&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● &lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;References: &lt;a href=&quot;https://flutter.dev/showcase/google-pay&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Google 공식 사례 연구&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Flutter Showcase |Google Pay&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;As Google Pay expanded to new regions &amp;mdash; each with its own unique user dynamics, geography, and challenges &amp;mdash; the team needed a more efficient way to make platform changes at scale.&quot; data-og-host=&quot;flutter.dev&quot; data-og-source-url=&quot;https://flutter.dev/showcase/google-pay&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nAg8i/hyYmPMxQMw/fE466Ue2QvEDZfk8E0OhO1/img.png?width=800&amp;amp;height=378&amp;amp;face=0_0_800_378&quot; data-og-url=&quot;https://flutter.dev/showcase/google-pay/&quot;&gt;&lt;a href=&quot;https://flutter.dev/showcase/google-pay/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.dev/showcase/google-pay&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nAg8i/hyYmPMxQMw/fE466Ue2QvEDZfk8E0OhO1/img.png?width=800&amp;amp;height=378&amp;amp;face=0_0_800_378');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter Showcase |Google Pay&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;As Google Pay expanded to new regions &amp;mdash; each with its own unique user dynamics, geography, and challenges &amp;mdash; the team needed a more efficient way to make platform changes at scale.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 인도에서 수억 명이 사용하는 Google Pay는 Flutter로 개발되었으며, 사기 탐지 및 사용자 행동 분석에 Google의 AI 기술을 활용합니다. Google 자체의 앱에서 Flutter를 선택했다는 점은 구글이 Flutter의 미래에 강한 확신이 있음을 보여줍니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;2. &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.ebay.motorsapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;eBay Motors&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● &lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;References: &lt;a href=&quot;https://innovation.ebayinc.com/tech/product/ebay-motors-accelerating-with-fluttertm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;eBay 기술 블로그&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;eBay Motors: Accelerating With Flutter&amp;trade;&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;The UI software development kit enables a consistent user experience across iOS and Android.&quot; data-og-host=&quot;innovation.ebayinc.com&quot; data-og-source-url=&quot;https://innovation.ebayinc.com/tech/product/ebay-motors-accelerating-with-fluttertm/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5o079/hyYnaC3idp/5kcSsDthc1CQhdlZwuvaQ1/img.jpg?width=1024&amp;amp;height=575&amp;amp;face=0_0_1024_575,https://scrap.kakaocdn.net/dn/cYemqr/hyYmIzRgBx/LfDvRBlNhbAEtYw5wzVje1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/dHgQ4D/hyYmX4RdWB/hSTDyI3yvJAhHnWxK7U5eK/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450&quot; data-og-url=&quot;https://innovation.ebayinc.com/tech/product/ebay-motors-accelerating-with-fluttertm/&quot;&gt;&lt;a href=&quot;https://innovation.ebayinc.com/tech/product/ebay-motors-accelerating-with-fluttertm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://innovation.ebayinc.com/tech/product/ebay-motors-accelerating-with-fluttertm/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5o079/hyYnaC3idp/5kcSsDthc1CQhdlZwuvaQ1/img.jpg?width=1024&amp;amp;height=575&amp;amp;face=0_0_1024_575,https://scrap.kakaocdn.net/dn/cYemqr/hyYmIzRgBx/LfDvRBlNhbAEtYw5wzVje1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/dHgQ4D/hyYmX4RdWB/hSTDyI3yvJAhHnWxK7U5eK/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;eBay Motors: Accelerating With Flutter&amp;trade;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The UI software development kit enables a consistent user experience across iOS and Android.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;innovation.ebayinc.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● eBay의 자동차 판매 앱은 Flutter로 개발되었으며, 차량 이미지 인식 &amp;amp; 가격 예측에 머신러닝을 활용합니다. eBay와 같은 글로벌 이커머스 기업이 Flutter를 선택한 것은 그 확장성과 AI 통합 가능성을 인정했다는 증거입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;3. &lt;a href=&quot;https://play.google.com/store/apps/details?id=de.bmw.connected.mobile20.row&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;My BMW&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● &lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Refrences: &lt;a href=&quot;https://flutter.dev/showcase/bmw&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Flutter Showcase&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Flutter Showcase | BMW&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;BMW Group is one of the most storied and innovative automobile manufacturers ever. As BMW has always been a spearhead for premium individual mobility in the automobile industry, the company is choosing Flutter for its mobile app platform.&quot; data-og-host=&quot;flutter.dev&quot; data-og-source-url=&quot;https://flutter.dev/showcase/bmw&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zdOIi/hyYndT5yvo/hNUXeKsxKQNbBMwXKSEmE1/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533&quot; data-og-url=&quot;https://flutter.dev/showcase/bmw/&quot;&gt;&lt;a href=&quot;https://flutter.dev/showcase/bmw/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.dev/showcase/bmw&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zdOIi/hyYndT5yvo/hNUXeKsxKQNbBMwXKSEmE1/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter Showcase | BMW&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BMW Group is one of the most storied and innovative automobile manufacturers ever. As BMW has always been a spearhead for premium individual mobility in the automobile industry, the company is choosing Flutter for its mobile app platform.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● BMW의 공식 앱은 Flutter로 개발되었으며, 차량 상태 진단 &amp;amp; 예측 유지 보수에 AI 기술을 접목했습니다. 프리미엄 자동차 브랜드가 Flutter를 선택했다는 것은 Flutter의 안정성과 성능을 보증합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;4. &lt;a href=&quot;https://play.google.com/store/apps/details/Reflectly_Mood_Tracker_Diary?id=com.reflectlyApp&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Reflectly&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● &lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Refrences: &lt;a href=&quot;https://flutter.dev/showcase/reflectly&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Flutter Showcase&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Flutter Showcase | Reflectly&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Despite having just two engineers, the Reflectly team was able to deliver both an iOS and Android app in just 2.5 months. Watch the video above to learn more about how the team accomplished this feat.&quot; data-og-host=&quot;flutter.dev&quot; data-og-source-url=&quot;https://flutter.dev/showcase/reflectly&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://flutter.dev/showcase/reflectly/&quot;&gt;&lt;a href=&quot;https://flutter.dev/showcase/reflectly/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.dev/showcase/reflectly&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('\'\'');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter Showcase | Reflectly&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Despite having just two engineers, the Reflectly team was able to deliver both an iOS and Android app in just 2.5 months. Watch the video above to learn more about how the team accomplished this feat.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 인공지능 일기 앱인 Reflectly는 Flutter로 개발되었으며, 사용자의 감정 분석을 위해 자연어 처리 AI를 활용합니다. 수백만 명의 사용자를 보유한 Reflectyl는 Flutter의 초기 성공 사례 중 하나입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;5. &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.hamilton.app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Hamilton&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp; &amp;nbsp; ● &lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Refrences:&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;a href=&quot;https://flutter.dev/showcase/hamilton&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Flutter Showcase&lt;/span&gt;&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Flutter Showcase | Hamilton&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;When the team at Hamilton decided to build an app to engage fans both in and outside of the theater, they knew they needed an app. Watch the video to learn why the team turned to Flutter to help them expand the Hamilton brand beyond the stage.&quot; data-og-host=&quot;flutter.dev&quot; data-og-source-url=&quot;https://flutter.dev/showcase/hamilton&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://flutter.dev/showcase/hamilton/&quot;&gt;&lt;a href=&quot;https://flutter.dev/showcase/hamilton/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.dev/showcase/hamilton&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('\'\'');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter Showcase | Hamilton&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When the team at Hamilton decided to build an app to engage fans both in and outside of the theater, they knew they needed an app. Watch the video to learn why the team turned to Flutter to help them expand the Hamilton brand beyond the stage.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 브로드웨이 뮤지컬 Hamilton의 공식 앱은 Flutter로 개발되었으며, Google Cloud의 음성 인식 기술을 통합하여 노래 가사 검색 &amp;amp; 번역 기능을 제공합니다. 엔터테이먼트 산업에서도 Flutter의 활용이 확산하고 있음을 보여주는 사례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;6. &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.taobao.idlefish&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;알리바바&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;● &lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Refrences: &lt;a href=&quot;https://www.youtube.com/watch?v=jtYk3gWRSw0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;동영상에서 1분 35초 이후 Flutter 언급&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=jtYk3gWRSw0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eSILm/hyYm2ruOBX/I9XR7eKaUnzLY3VhY5tpT0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/xNHKI/hyYmO1bru8/DF1ncUbTuzUF2lyyYws4i1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Alibaba used Flutter to build 50+ million user Xianyu app (Flutter Developer Story)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/jtYk3gWRSw0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 중국의 대형 이커머스 플랫폼인 알리바바는 여러 앱에 Flutter를 도입했으며, 특히 이미지 인식 AI를 통합하여 제품 카테고리화 &amp;amp; 가격 추천 기능을 제공합니다. 수억 명의 사용자를 보유한 알리바바에서 Flutter를 사용한다는 것은 그 확장성을 증명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #456771;&quot;&gt;&lt;b&gt;◎ Flutter와 Google AI의 미래 전망&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt; └ Flutter와 Google AI의 결합은 현재 진행 중인 개발 동향과 Google의 공식 로드맵을 바탕으로 다음과 같은 구체적인 발전이 예상됩니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. Gemini API의 Flutter 공식 지원&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● Google은 2024년 Google I/O에서 Flutter를 위한 &lt;a href=&quot;https://io.google/2024/intl/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Gemini API 지원을 발표&lt;/span&gt;&lt;/a&gt;했습니다. 현재 &lt;a href=&quot;https://pub.dev/packages/google_generative_ai&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;google_generative_ai&lt;/span&gt;&lt;/a&gt; 라이브러리가 제공되고 있으며, 이를 통해 Fluitter 개발자들은 텍스트 생성 &amp;amp; 코드 완성 &amp;amp; 이미지 분석... 등 Gemini의 다양한 기능을 앱에 통합할 수 있습니다. Google의 개발 패턴을 볼 때, 이 패키지는 안정화 단계에 들어갈 것으로 예상되며, 앞으로 더 많은 Gemini 기능이 Flutter 생태계에 통합될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. TensorFlow Lite 및 ML Kit의 진화&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● Google은 Flutter를 위한 TensorFlow Lite 지원을 지속적으로 강화하고 있습니다. &lt;a href=&quot;https://pub.dev/packages/tflite_flutter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;tflite_flutter&lt;/span&gt;&lt;/a&gt; 라이브러리의 최근 업데이트와 &lt;a href=&quot;https://developers.google.com/ml-kit?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;ML Kit의 Flutter 지원&lt;/span&gt;&lt;/a&gt;을 통해 이미 온디바이스 AI 기능을 쉽게 통합할 수 있습니다. 특히 주목할 점은 Google이 &lt;a href=&quot;https://blog.tensorflow.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;TensorFlow 공식 블로그&lt;/span&gt;&lt;/a&gt;에서 발표한 TensorFlow Lite의 성능 최적화와 모델 압축 기술로, 이는 Flutter 앱의 온디바이스 AI 성능을 크게 향상시킬 것입니다. 또한, TensorFlow 팀은 &lt;a href=&quot;https://www.tensorflow.org/lite/performance/best_practices?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;성능 최적화 모범 사례&lt;/span&gt;&lt;/a&gt;를 통해 모바일 기기에서 모델 최적화와 하드웨어 가속기 활용 방법을 지속적으로 발전시키고 있습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333;&quot;&gt;&lt;b&gt;3. Flutter 3.X의 AR 지원 강화&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● Flutter 팀은 &lt;a href=&quot;https://github.com/flutter/flutter/blob/master/docs/roadmap/Roadmap.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;공식 로드맵&lt;/span&gt;&lt;/a&gt;에서 AR 지원 강화를 우선순위로 두고 있습니다. 현재 &lt;a href=&quot;https://pub.dev/packages/arcore_flutter_plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;arcore_flutter_plugin&lt;/span&gt;&lt;/a&gt;과 &lt;a href=&quot;https://pub.dev/packages/ar_flutter_plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;ar_flutter_plugin&lt;/span&gt;&lt;/a&gt; 같은 라이브러리를 통해 AR 기능을 구현할 수 있지만, Google은 ARCore와 Flutter의 더 긴밀한 통합을 위한 공식 플러그인을 개발 중입니다. 이는 앞으로 Flutter 앱에서 AI로 강화된 AR 경험을 더 쉽게 구축할 수 있게 할 것입니다. 특히 구글의 AR 개발 플랫폼인 ARCore의 최신 기능인 &lt;a href=&quot;https://developers.google.com/ar/develop/java/geospatial/enable?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Geospatial API&lt;/span&gt;&lt;/a&gt;와 &lt;a href=&quot;https://developers.google.com/ar/develop/augmented-images?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;이미지 추적 기능&lt;/span&gt;&lt;/a&gt;이 Flutter에서도 손쉽게 사용될 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333;&quot;&gt;&lt;b&gt;4. Firebase와 ML 서비스의 통합 강화&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● Google의 Firebase는 이미 Flutter와 긴밀하게 통합되어 있으며, &lt;a href=&quot;https://pub.dev/packages/firebase_ml_model_downloader&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;firebase_ml_model_downloader&lt;/span&gt;&lt;/a&gt;를 통해 커스텀 ML 모델을 Flutter 앱에 쉽게 배포할 수 있습니다. Google의 최근 개발 방향을 보면, Firebase와 Google Cloud AI 서비스 간의 통합이 더욱 강화되고 있어, Flutter 개발자들은 앞으로 서버리스 환경에서도 고급 AI 기능을 쉽게 활용할 수 있을 것입니다. 실제로 &lt;a href=&quot;https://firebase.blog/posts/2024/05/introducing-vertex-ai-firebase/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Google Cloud의 Vertex AI와 Firebase의 통합&lt;/span&gt;&lt;/a&gt;이 이미 진행 중이며, 이는 Flutter 앱에서도 활용할 수 있을 것으로 기대됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333;&quot;&gt;&lt;b&gt;5. 엔터프라이즈 솔루션을 위한 성능 최적화&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ● Google은 Flutter 3.0 이후 엔터프라이즈 앱 지원에 상당한 투자를 하고 있습니다. Flutter 팀의 &lt;a href=&quot;https://medium.com/flutter/whats-new-in-flutter-3-13-479d9b11df4d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;공식 발표&lt;/span&gt;&lt;/a&gt;에 따르면, 대규모 앱에서의 성능 최적화와 메모리 관리 개선이 계속되고 있습니다. 이러한 개선은 데이터 처리가 많은 AI 기반 기업용 앱에 특히 중요합니다. BMW와 같은 기업의 사례에서 볼 수 있듯이, Flutter는 이미 엔터프라이즈급 앱 개발에 적합한 선택으로 자리 잡고 있으며, Google의 AI 서비스와의 통합은 이러한 추세를 더욱 강화할 것입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이러한 발전 방향은 Google의 공식 발표와 개발 로드맵, 그리고 현재의 라이브러리 개발 트렌드를 기반으로 한 것으로, Flutter와 Google AI의 통합이 더욱 깊어질 것임을 보여줍니다. 특히 Google이 AI 기술 개발에 대규모 투자를 하는 현시점에서, Flutter는 이러한 AI 기술을 모바일 앱에 구현하는 가장 효율적인 도구로 자리매김할 가능성이 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #456771; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;◎ 결론&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;└ &lt;/span&gt;&lt;/b&gt;Flutter는 단순히 UI 프레임워크를 넘어, AI 시대에 필수적인 개발 도구로 자리매김하고 있습니다. Google의 AI 생태계와의 긴밀한 통합 &amp;amp; 크로스플랫폼 능력, 그리고 &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Google Pay &amp;amp; eBay Motors &amp;amp; My BMW &amp;amp; Reflectly &amp;amp; Hamilton &amp;amp; 알리바바&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;... 등 대규모 기업에서 Flutter를 사용한 것은 Flutter의 확장성과 안정성을 보여줍니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이러한 사례들은 Flutter가 단순한 UI 개발 도구가 아닌 엔터프라이즈급 애플리케이션에도 적합하다는 것을 증명합니다. 특히 Google이 자체 AI 기술을 Flutter와 통합하려는 노력은 앞으로 Flutter 개발자들이 더 쉽게 AI 기능을 구현할 수 있는 환경을 만들어 줄 것입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;개발자로서 &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Flutter와 Google의 AI 기술을 함께 학습하고 활용하는 것은 미래를 준비하는 현명한 투자&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;가 될 것&lt;/span&gt;입니다. 기술의 발전 속도가 빨라지는 이 시대에, Flutter는 개발자에게 효율성과 혁신적인 가능성을 동시에 제공하는 강력한 도구입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;긴 글을 읽어주셔서 감사합니다!&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;처음 Flutter를 접했을 때는 단순히 또 하나의 크로스플랫폼 프레임워크라고 생각했습니다. 하지만 점점 더 많은 기업이 Flutter를 채택하고, Google이 지속적으로 투자하는 모습을 보며 그 가능성을 깨닫게 되었습니다.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;특히 AI 기술이 일상화되는 시대에 Flutter의 역할이 더욱 중요해질 것으로 생각합니다. 제가 이 글을 통해 전하고 싶은 메시지는 단순합니다. Flutter는 단지 지금의 트렌드가 아니라, AI와 함께 발전하며 앞으로 모바일 개발의 중요한 축이 될 것이라는 점입니다.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;저 역시 개발자로서 이러한 변화와 함께 성장하고 있으며, 앞으로 더 많은 개발자가 Flutter의 가능성을 발견하길 바랍니다. 여러분의 개발 여정에 이 글이 작은 도움이 되었기를 희망합니다.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;감사합니다!&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT &amp;middot; 테크/기술 트렌드</category>
      <category>AI개발</category>
      <category>flutter</category>
      <category>Gemini</category>
      <category>GoogleAI</category>
      <category>TensorFlow</category>
      <category>개발자</category>
      <category>기술트렌드</category>
      <category>모바일앱</category>
      <category>인공지능</category>
      <category>크로스플랫폼</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/577</guid>
      <comments>https://kwonputer.tistory.com/577#entry577comment</comments>
      <pubDate>Thu, 6 Mar 2025 00:05:14 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 면접 질문을 통해 알아보는 플러터 (4) - 경험</title>
      <link>https://kwonputer.tistory.com/574</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;안녕하세요!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 포스트에서는 '경험'에 대한 면접 질문에 대해서 다루겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;실제 면접이라고 생각하고 포스트를 작성해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;주관적인 생각을 바탕으로 작성한 포스트라서 참고만 부탁드립니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정확한 정보전달이 아닐 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 아래의 목차를 참고해 주세요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;1. 개발을 진행하면서 어려움을 느꼈던 순간과 어려움을 해결하기 위한 노력을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;2. 코드 리뷰를 할 때, 중점적으로 보는 것은 어떤 것인지 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;3. 팀원간의 갈등 상황을 어떻게 해결하는지 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;4. 학습하는 방법과 정보를 얻는 채널을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;5. 개발자가 된 이유와 개발 직군에서 모바일 개발을 선택한 이유를 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;6. 최근에 읽은 책이나 기억에 남는 글이 있다면 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;7. 좋은 코드에 대한 생각을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;&lt;i&gt;큰 범위의 개념에 대한 질문&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;&lt;a href=&quot;https://kwonputer.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/572&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741066947870&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (2) - 개념&quot; data-og-description=&quot;https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 &quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/572&quot; data-og-url=&quot;https://kwonputer.tistory.com/572&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QRySF/hyYmU0RUIf/ggFxZbb5RCIbRntABe19D1/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284,https://scrap.kakaocdn.net/dn/b8acGU/hyYndTHO5A/tJ6KjHLPrA5KfoOE8Xt48K/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/572&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QRySF/hyYmU0RUIf/ggFxZbb5RCIbRntABe19D1/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284,https://scrap.kakaocdn.net/dn/b8acGU/hyYndTHO5A/tJ6KjHLPrA5KfoOE8Xt48K/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (2) - 개념&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;1. 알고 있는 디자인 패턴에 대해서 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;2. MVC, MVP, MVVM 패턴의 차이점을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;3. 싱글톤 디자인 패턴의 장점과 단점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;4. 클린 아키텍처(Clean Architecture)를 반드시 사용해야 하는 이유를 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;5. 의존성 역전에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;6. 스레드와 프로세스, 멀티 스레드와 멀티 프로세스에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;7. 이미지 캐시(Memory, Disk)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;8. 해시(Hash)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;9. 대칭 키와 비대칭 키에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;10. 동기(Synchronous)와 비동기(Asynchronous)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;11. 접근 토큰(Access Token)과 갱신 토큰(Refresh Token)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;12. TCP와 UDP에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;13. Git의 Merge와 Rebase의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;14. GraphQL에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;15. CI/CD에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;16. 선언형 &amp;amp; 명령형 &amp;amp; 함수형 프로그래밍에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;17. 메모리 누수(Memory Leak)에 대한 설명과 방지하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;18. BDD &amp;amp; TDD의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;19. SDK 개발과 서비스 개발의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;20. HTTP와 HTTPS의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;21. HTTPS의 SSL Handshaking에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;i&gt;Flutter 기술 질문&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;&lt;a href=&quot;https://kwonputer.tistory.com/573&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/573&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741066888575&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험&quot; data-og-description=&quot;※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.https://kwonputer.tistory.com/571&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (1) - 목차안&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/573&quot; data-og-url=&quot;https://kwonputer.tistory.com/573&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NYMxd/hyYm2kkaM0/1CJRQKtOG1OaZ1vs9Kjbxk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997,https://scrap.kakaocdn.net/dn/L114B/hyYjlMpevn/UbVIT8gXBVF9hNlLjAgOkk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/573&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/573&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NYMxd/hyYm2kkaM0/1CJRQKtOG1OaZ1vs9Kjbxk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997,https://scrap.kakaocdn.net/dn/L114B/hyYjlMpevn/UbVIT8gXBVF9hNlLjAgOkk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.https://kwonputer.tistory.com/571&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (1) - 목차안&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;1. Flutter의 장점과 단점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;2. Stateless &amp;amp; Stateful Widget의 차이점을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;3. Flutter에서 상태 관리는 어떻게 하는지 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;4. Riverpod에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;5. Bloc에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;6. Provider에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;7. GetX에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;8. 위젯 트리 구조에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;9. Flutter에서 비동기 프로그래밍을 하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;10. Flutter에서 API 호출을 하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;11. Hot Reload와 Hot Restart의 차이점을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;12. Flutter에서 Native 코드와의 통합 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;13. Flutter에서 라우팅과 네비게이션을 처리하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;14. Flutter의 Form 위젯과 Form 검증 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;15. Build Context의 context의 역할에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;16. Flutter의 Key에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;17. Flutter의 Stream과 Stream 유형에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;18. FutureBuilder와 StreamBuilder의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;19. Flutter의 3가지 테스트(단위 테스트, 위젯 테스트, 통합 테스트)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;20. WidgetsApp과 MaterialApp의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;21. Abstract (extends) &amp;amp; Interface (implements) &amp;amp; Mixin (with)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&lt;i&gt;경험에 대한 질문&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #8a3db6;&quot;&gt;개발을 진행하면서 어려움을 느꼈던 순간과 어려움을 해결하기 위한 노력을 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 저는 SI 업체에서 일하며 백엔드와의 협업에서 어려움을 겪었습니다. 한정된 프로젝트 기간 내에 백엔드 작업 지연이나 데이터 구조 변경으로 인해 클라이언트 개발에 큰 압박을 받았습니다. 이러한 &lt;span style=&quot;color: #009a87;&quot;&gt;외부 의존성 문제를 해결하기 위해 클린 아키텍처를 도입&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처를 적용함으로써 외부 시스템 변경에 덜 영향을 받는 코드 구조를 만들었습니다. 데이터 계층 &amp;amp; 도메인 계층 &amp;amp; 프레젠테이션 계층을 명확히 분리하여 백엔드 API가 변경되더라도 데이터 계층만 수정하면 되도록 설계했습니다. 또한, 추상화된 인터페이스와 의존성 주입을 통해 백엔드 응답이 지연되는 상황에서도 Mock 데이터를 활용해 UI 개발을 진행할 수 있었습니다. 이에 따라, 외부 요인에 의한 개발 지연을 최소화하고 프로젝트 기간을 준수할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;color: #8a3db6;&quot;&gt;코드 리뷰를 할 때, 중점적으로 보는 것은 어떤 것인지 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 먼저 프로젝트의 코드 규칙에 맞는지 확인합니다. 줄바꿈 &amp;amp; 네이밍 컨벤션 &amp;amp; 주석... 등등 해당 프로젝트의 코드 규칙에 어긋나는 것은 없는지를 먼저 체크합니다. 그다음으로는 &lt;span style=&quot;color: #009a87;&quot;&gt;코드의 안정성과 확장성을 중점적&lt;/span&gt;으로 봅니다. 코드에 문제가 생겼을 때, 버그의 영향 범위가 제한적인지, 다른 부분에서 사이드 이펙트를 일으키지 않는지 검토합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;color: #8a3db6;&quot;&gt;팀원간의 갈등 상황을 어떻게 해결하는지 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 의견 충돌로 인한 갈등 상황이 발생했을 때, 저는 먼저 상대방의 입장을 경청하고 공감하는 자세를 취합니다. 하지만 공과 사는 명확히 구분해야 한다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;공적인 측면&lt;/span&gt;에서는 프로젝트와 코드 품질을 위해 제 의견을 논리적으로 전달하고, 더 나은 방향으로 나아가기 위한 건설적인 제안을 합니다. 필요하다면 객관적인 데이터나 문서를 통해 제 주장을 뒷받침합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;사적인 측면&lt;/span&gt;에서는 최대한 친근하고 웃는 얼굴로 대화하며 좋은 관계를 유지합니다. 이렇게 함으로써 상대방이 &quot;이 사람은 사적으로는 좋은 동료이지만, 공적으로는 일을 잘하기 위한 고집이 있다.&quot;라고 인식하게 됩니다. 결국 상호 존중을 바탕으로 한 솔직한 의사소통이 갈등 해결의 핵심이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;span style=&quot;color: #8a3db6;&quot;&gt;학습하는 방법과 정보를 얻는 채널을 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 정보를 얻는 주요 채널은 IT 뉴스와 블로그입니다. 학습 방법은 &lt;span style=&quot;color: #009a87;&quot;&gt;실전 중심적&lt;/span&gt;입니다. 뉴스나 블로그에서 얻은 키워드가 있으면, 해당 키워드를 중심으로 공식 문서를 찾아보고 직접 구현해 보는 방식을 선호합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;span style=&quot;color: #8a3db6;&quot;&gt;개발자가 된 이유와 개발 직군에서 모바일 개발을 선택한 이유를 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 어릴 때부터 호기심이 많고 창작에 대한 욕구가 강했으며, 소설가와 화가를 꿈꿨던 적도 있습니다. 비록 글이나 그림에는 재능이 부족했지만, 개발도 창작의 영역이라고 생각합니다. 제 손으로 &lt;span style=&quot;color: #009a87;&quot;&gt;무언가를 만들어 세상에 내놓는 과정에서 큰 만족감&lt;/span&gt;을 느낍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 개발을 선택한 이유는 프론트엔드 &amp;amp; 백엔드 &amp;amp; 모바일을 모두 경험해 본 결과, 모바일이 가장 흥미로웠기 때문입니다. 모바일 개발은 결과물이 눈에 직접 보이고 사용자들의 일상생활에 직접적인 영향을 미치는 점이 매력적이었습니다. 또한 현대 사회에서 스마트폰의 접근성이 좋아서 개발한 앱을 많은 사람들이 쉽게 사용할 수 있다는 점도 큰 장점으로 다가왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. &lt;span style=&quot;color: #8a3db6;&quot;&gt;최근에 읽은 책이나 기억에 남는 글이 있다면 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 최근에 &lt;span style=&quot;color: #009a87;&quot;&gt;&quot;트럼프 2.0시대의 2025년 AI 트렌드 10가지&quot;&lt;/span&gt;라는 글을 읽었는데, 개발자로서 많은 통찰을 얻었습니다. 이 글은 정치적 변화가 기술 발전에 미치는 영향부터 AI가 물리 세계와 융합되는 미래까지 폭넓게 다루고 있어서 인상적이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 기억에 남는 부분은 AI가 단순 기술을 넘어 국가 경쟁력의 핵심으로 부상하고 있다는 점과, 이로 인해 데이터센터와 에너지 인프라가 전략적 자산이 되고 있다는 분석입니다. 미국에서는 AI 데이터센터의 전력 수요를 충족하기 위해 폐쇄되었던 원자력 발전소를 재가동하는 사례도 언급되었는데, 이는 AI 발전 속도와 규모를 실감하게 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, AI 시장이 초기 도입 단계에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]ROI&lt;/span&gt;를 중시하는 단계로 전환되면서, 특정 산업이나 문제에 특화된 버티컬 AI 서비스들이 성장하고 있다는 부분도 주목했습니다. 이는 모바일 앱 개발에도 적용할 수 있는 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]인사이트&lt;/span&gt;라고 생각합니다. 앱 개발에서도 특정 산업이나 사용자 문제에 초점을 맞춘 솔루션이 더 가치 있는 경험을 제공할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 AI 아바타 기술을 통해 언어 장벽이 사라질 수 있다는 전망은 개발자로서 새로운 가능성을 떠올리게 했습니다. 크로스 플랫폼 개발을 통해 기술적 장벽을 낮추는 것처럼, AI는 언어와 문화적 장벽을 허물어 더 포용적인 기술 생태계를 만들 수 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 통해 기술 트렌드를 단순히 기술적 관점이 아닌 사회, 경제, 정치적 맥락에서 이해하는 것의 중요성을 배웠고, 이런 통합적 시각이 앞으로 더 혁신적인 앱 개발에도 도움이 될 것이라 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]ROI(Return On Investement, 투자수익률)&lt;/span&gt;: 투자한 비용 대비 얻은 이익의 비율을 나타내는 지표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]인사이트(Insight)&lt;/span&gt;: 단순한 정보나 데이터를 넘어서 그 속에 담긴 의미와 패턴을 발견하고 이해하는 통찰력을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. &lt;span style=&quot;color: #8a3db6;&quot;&gt;좋은 코드에 대한 생각을 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ 좋은 코드란 &lt;span style=&quot;color: #009a87;&quot;&gt;&quot;대화가 필요 없는 코드&quot;&lt;/span&gt;라고 생각합니다. 즉, 코드 자체가 명확하고 이해하기 쉬워서 별도의 설명 없이도 다른 개발자가 쉽게 이해하고 유지보수할 수 있는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 성능 최적화와 실행 속도는 개발자로서 당연히 챙겨야 할 기본적인 부분입니다. 그러나 그것을 넘어서 프로젝트의 코드 규칙을 정확히 준수하고, 어떤 개발자가 보더라도 추가적인 질문 없이 코드의 의도와 흐름을 파악할 수 있는 것이 진정한 좋은 코드라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴&amp;nbsp;글을&amp;nbsp;끝까지&amp;nbsp;읽어주셔서&amp;nbsp;감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 작성하면서 저 역시 많은 생각을 하게 되었습니다. 개발자로서의 면접은 단순히 기술적 지식을 확인하는 과정을 넘어, 문제 해결 능력과 협업 방식, 그리고 개인의 성장 가능성을 보여주는 중요한 기회라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 경험에 관한 질문들은 단순 암기로는 대답할 수 없는, 진정한 자신의 이야기를 들려줄 수 있는 부분이라 더욱 중요하게 느껴집니다. 저 자신도 이러한 질문들에 대해 깊이 생각하며 개발자로서의 방향성을 다시 한번 점검하는 소중한 시간이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분도 자신만의 이야기를 정리하면서 개발자로서의 정체성을 더 단단히 하고, 빛나는 순간을 만드시길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 화이팅입니다! 여러분의 개발 여정을 진심으로 응원합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <category>flutter 개발자</category>
      <category>flutter 면접 질문</category>
      <category>개발자 경험 공유</category>
      <category>모바일 개발 면접</category>
      <category>앱 개발자</category>
      <category>코드 리뷰</category>
      <category>클린 아키텍처</category>
      <category>테크인터뷰</category>
      <category>팀 협업</category>
      <category>플러터 상태관리</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/574</guid>
      <comments>https://kwonputer.tistory.com/574#entry574comment</comments>
      <pubDate>Tue, 4 Mar 2025 16:54:53 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술</title>
      <link>https://kwonputer.tistory.com/573</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.&lt;/i&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/572&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741066741286&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (2) - 개념&quot; data-og-description=&quot;https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 &quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/572&quot; data-og-url=&quot;https://kwonputer.tistory.com/572&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QRySF/hyYmU0RUIf/ggFxZbb5RCIbRntABe19D1/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284,https://scrap.kakaocdn.net/dn/b8acGU/hyYndTHO5A/tJ6KjHLPrA5KfoOE8Xt48K/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/572&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QRySF/hyYmU0RUIf/ggFxZbb5RCIbRntABe19D1/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284,https://scrap.kakaocdn.net/dn/b8acGU/hyYndTHO5A/tJ6KjHLPrA5KfoOE8Xt48K/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (2) - 개념&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 'Flutter 기술'에 대한 면접 질문에 대해서 다루겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 면접이라고 생각하고 포스트를 작성해 보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;주관적인 생각을 바탕으로 작성한 포스트라서 참고만 부탁드립니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정확한 정보전달이 아닐 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래의 목차를 참고해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;1. Flutter의 장점과 단점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;2. Stateless &amp;amp; Stateful Widget의 차이점을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;3. Flutter에서 상태 관리는 어떻게 하는지 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;4. Riverpod에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;5. Bloc에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;6. Provider에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;7. GetX에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;8. 위젯 트리 구조에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;9. Flutter에서 비동기 프로그래밍을 하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;10. Flutter에서 API 호출을 하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;11. Hot Reload와 Hot Restart의 차이점을 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;12. Flutter에서 Native 코드와의 통합 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;13. Flutter에서 라우팅과 네비게이션을 처리하는 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;14. Flutter의 Form 위젯과 Form 검증 방법에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;15. Build Context의 context의 역할에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;16. Flutter의 Key에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;17. Flutter의 Stream과 Stream 유형에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;18. FutureBuilder와 StreamBuilder의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;19. Flutter의 3가지 테스트(단위 테스트, 위젯 테스트, 통합 테스트)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;20. WidgetsApp과 MaterialApp의 차이점에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;21. Abstract (extends) &amp;amp; Interface (implements) &amp;amp; Mixin (with)에 대해 말해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter의 장점과 단점에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter는 Google이 개발한 크로스 플랫폼 프레임워크로, 단일 코드베이스로 여러 플랫폼에서 고성능 앱을 개발할 수 있지만, 앱 크기가 상대적으로 크고 일부 플랫폼별 기능에 제한이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Flutter의 주요 &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 크로스 플랫폼 개발: 하나의 코드베이스로 AOS, IOS, 웹, 데스크톱 앱을 모두 개발할 수 있어서 개발 시간과 비용을 절감할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 핫리로드: 코드 변경 사항을, 앱을 재시작하지 않고도 즉시 확인할 수 있어 개발 속도가 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 풍부한 위젯 라이브러리: Material Design과 Cupertino 스타일의 다양한 내장 위젯을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 자체 렌더링 엔진: &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]Skia 그래픽 엔진&lt;/span&gt;을 사용하여 플랫폼과 관계없이 일관된 UI를 제공합니다.&lt;br /&gt;5. 우수한 성능: &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]JIT(Just-In-Time) &amp;amp; AOT(Ahead-Of-Time)&lt;/span&gt; 컴파일을 모두 지원하여 개발 및 배포 환경에서 최적의 성능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Flutter의 주요 &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 앱 크기: 기본 앱 크기가 네이티브 앱보다 상대적으로 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 플랫폼별 기능 제한: 일부 플랫폼 특화 기능은 별도의 플러그인이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 학습 곡선: Dart 언어와 위젯 기반 프로그래밍 패러다임에 익숙해지는 데에 시간이 필요할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 새로운 플랫폼 업데이트 지연: 새로운 네이티브 플랫폼 기능이 Flutter에 적용되기까지 시간이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]Skia 그래픽 엔진&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &lt;b&gt;Skia란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Skia는 Google이 개발하고 관리하는 오픈 소스 2D 그래픽 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Flutter뿐만 아니라 Chrome &amp;amp; Android &amp;amp; Firefox &amp;amp; Google Photos... 등 다양한 제품에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &lt;b&gt;Flutter에서의 역할&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Flutter는 Skia를 내장하여 플랫폼에 독립적인 렌더링 시스템을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 네이티브 UI 컴포넌트가 아닌 직접 화면에 픽셀을 그리는 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 이를 통해 Flutter는 AOS/IOS... 등 다양한 플랫폼에서 완전히 동일한 UI 렌더링이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 플랫폼 간 일관된 UI 표현이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 플랫폼 네이티브 위젯에 의존하지 않아 OS 버전에 따른 UI 차이가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 복잡한 커스텀 애니메이션이나 그래픽 효과를 모든 플랫폼에서 동일하게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 60fps(초당 60프레임)의 부드러운 애니메이션과 전환 효과를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]JIT(Just-In-Time) &amp;amp; AOT(Ahead-Of-Time) 컴파일&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 개발과 배포 환경에서 서로 다른 컴파일 방식을 사용하여 최적의 개발 경험과 런타임 성능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &lt;b&gt;JIT(Just-In-Time) 컴파일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 개발 환경에서 사용: Flutter 개발 중에 주로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 작동 방식: 코드가 실행되는 시점에 필요한 부분만 실시간으로 컴파일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 핫 리로드 가능: 코드 변경 후, 전체 재컴파일 없이 변경된 부분만 갱신하여 실시간으로 결과를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 빠른 개발 주기: 변경 사항을 즉시 확인할 수 있어 개발 속도가 향상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 디버깅 정보 유지: 런타임에 더 많은 정보를 유지하여 디버깅이 용이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 초기 실행 속도가 상대적으로 느리고, 런타임 오버헤드가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &lt;b&gt;AOT(Ahead-Of-Time) 컴파일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 배포 환경에서 사용: 앱 릴리스 빌드 시 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 작동 방식: 앱이 실행되기 전에 모든 코드를 미리 네이티브 코드로 컴파일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 빠른 실행 속도: 앱 시작 및 실행 속도가 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 낮은 메모리 사용량: 실행 시, 추가 컴파일 과정이 필요 없어서 메모리 효율이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 최적화된 성능: 런타임에 추가 분석이나 컴파일 없이 최적화된 네이티브 코드를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 예측 가능한 성능: 런타임 컴파일로 인한 지연이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 핫 리로드가 불가능하며, 코드 변경 시에 전체 앱을 다시 컴파일해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &lt;b&gt;Flutter에서의 활용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 개발 모드(debug): JIT 컴파일러를 사용합니다. 핫 리로드로 빠른 개발 경험을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 릴리스 모드(release): AOT 컴파일러를 사용합니다. 최적의 성능과 작은 앱 크기를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 프로필 모드(profile): AOT 컴파일을 사용하되 일부 디버깅 기능을 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이중 컴파일 전략은 Flutter의 큰 장점 중 하나로, 개발자는 개발 중에는 빠른 피드백 루프를 경험하면서도 최종 사용자에게는 네이티브에 가까운 성능의 앱을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Stateless &amp;amp; Stateful Widget의 차이점을 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Stateless Widget은 내부 상태를 가지지 않고, 한 번 빌드되면 변경되지 않지만, Stateful Widget은 내부 상태를 관리하고 상태가 변경될 때마다 재빌드됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Stateless Widget&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 불변(Immutable)하며 한 번 생성되면 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 내부 상태를 가지지 않으며, 외부에서 주입받은 데이터만 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● build 메서드는 위젯이 생성될 때 한번만 호출됩니다. (부모 위젯이 재빌드될 때는 제외)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 메모리 사용이 적고 성능이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 주로 정적인 UI 요소나 데이터 표시를 위해 사용됩니다. (ex. Text &amp;amp; Icon &amp;amp; Image... 등의 정적 위젯)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Stateful Widget&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 내부 상태(State 객체)를 가지며, 이 상태가 변경될 때 위젯이 재빌드 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 위젯 클래스와 상태 클래스(State) 두 개의 클래스로 구성됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● setState 메서드를 통해 상태 변경을 알리고 UI를 업데이트합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 다양한 수명 주기 메서드(initState, dipose... 등)를 가집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 사용자 입력 &amp;amp; 애니메이션 &amp;amp; 데이터 변경과 같은 동적 요소에 적합합니다. (ex. Checkbox &amp;amp; TextField &amp;amp; AnimatedContainer... 등의 동적 위젯)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ &lt;b&gt;적절한 사용&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 데이터가 변경되지 않는 UI 요소에는 Stateless Widget을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 사용자 상호 작용이나 데이터 변경에 따라 UI가 변경되어야 하는 경우 Stateful Widget을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 성능 최적화를 위해 가능한 작은 부분만 Stateful로 유지하는 것이 좋습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter에서 상태 관리는 어떻게 하는지 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter에서 단순한 앱은 setState로, 복잡한 앱은 Provider &amp;amp; Bloc &amp;amp; Riverpod &amp;amp; GetX... 등의 상태 관리 라이브러리를 사용하여 효율적으로 상태를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 기본 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● setState: Stateful Widget 내에서 로컬 상태를 관리하는 가장 기본적인 방법으로, 간단한 앱에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]InheritedWidget&lt;/span&gt;: 위젯 트리 아래로 데이터를 효율적으로 전달하는 방법으로, Flutter의 많은 상태 관리 솔루션의 기반이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 중간 규모 앱 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Provider: InheritedWidget을 기반으로 한 인기 있는 상태 관리 라이브러리로, 간단하면서도 강력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]Scoped Model&lt;/span&gt;: 모델 클래스를 통해 상태를 관리하고 UI와 로직을 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 대규모 앱 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Bloc(Business Logic Component): 이벤트와 상태를 분리하여 Stream 기반으로 상태를 관리하는 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Riverpod: Provider의 개선된 버전으로, 컴파일 타임 안전성과 테스트 용이성을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● GetX: 상태 관리 &amp;amp; 라우팅 &amp;amp; 종속성 주입을 제공하는 경량 솔루션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]Redux&lt;/span&gt;: 단방향 데이터 흐름과 예측할 수 있는 상태 변경을 제공하는 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]MobX&lt;/span&gt;: 관찰할 수 있는 상태와 반응형 프로그래밍 패러다임을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]InheritedWidget&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741044995341&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 데이터를 담을 InheritedWidget 정의
class CounterInheritedWidget extends InheritedWidget {
  final int counter;
  final Function incrementCounter;

  const CounterInheritedWidget({
    Key? key,
    required this.counter,
    required this.incrementCounter,
    required Widget child,
  }) : super(key: key, child: child);

  // 데이터 접근 헬퍼 메서드
  static CounterInheritedWidget of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType&amp;lt;CounterInheritedWidget&amp;gt;();
    assert(result != null, 'No CounterInheritedWidget found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(CounterInheritedWidget oldWidget) {
    return counter != oldWidget.counter;
  }
}

// 상태를 관리하는 상위 위젯
class CounterProvider extends StatefulWidget {
  final Widget child;

  const CounterProvider({Key? key, required this.child}) : super(key: key);

  @override
  _CounterProviderState createState() =&amp;gt; _CounterProviderState();
}

class _CounterProviderState extends State&amp;lt;CounterProvider&amp;gt; {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CounterInheritedWidget(
      counter: _counter,
      incrementCounter: _incrementCounter,
      child: widget.child,
    );
  }
}

// 사용 예시 - 데이터 소비 위젯
class CounterDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterData = CounterInheritedWidget.of(context);
    return Text('카운터: ${counterData.counter}');
  }
}

class CounterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterData = CounterInheritedWidget.of(context);
    return ElevatedButton(
      onPressed: () =&amp;gt; counterData.incrementCounter(),
      child: Text('증가'),
    );
  }
}

// 앱 구조
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterProvider(
        child: Scaffold(
          appBar: AppBar(title: Text('InheritedWidget 예시')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CounterDisplay(),
                CounterButton(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InheritedWidget은 Flutter의 기본 클래스로, 위젯 트리를 통해 데이터를 효율적으로 하위 위젯에 전달하는 메커니즘입니다. 위젯 트리의 상위에서 데이터를 정의하고, 하위 위젯에서는 'context.dependOnInheritedWidgetOfExactType&amp;lt;T&amp;gt;()'를 통해 해당 데이터에 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;주요 특징&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯 트리 아래로 데이터를 전파합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 데이터가 변경되면 해당 데이터에 의존하는 위젯만 다시 빌드됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Provider &amp;amp; Riverpod... 등 많은 상태 관리 라이브러리의 기반 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● BuildContext를 통해 데이터에 접근합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]Scoped Model&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741045245360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

// 1. Model 클래스 정의
class CounterModel extends Model {
  int _counter = 0;
  int get counter =&amp;gt; _counter;

  void increment() {
    _counter++;
    // 상태 변경을 알림
    notifyListeners();
  }
}

// 2. 앱에서 ScopedModel 사용
class MyApp extends StatelessWidget {
  final CounterModel model = CounterModel();

  @override
  Widget build(BuildContext context) {
    return ScopedModel&amp;lt;CounterModel&amp;gt;(
      model: model,
      child: MaterialApp(
        home: CounterPage(),
      ),
    );
  }
}

// 3. 모델 데이터 사용
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Scoped Model 예시')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text('버튼을 누른 횟수:'),
            // ScopedModelDescendant로 모델 데이터 접근
            ScopedModelDescendant&amp;lt;CounterModel&amp;gt;(
              builder: (context, child, model) {
                return Text(
                  '${model.counter}',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: ScopedModelDescendant&amp;lt;CounterModel&amp;gt;(
        builder: (context, child, model) {
          return FloatingActionButton(
            onPressed: model.increment,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          );
        },
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scoped Model은 InheritedWidget을 기반으로 만들어진 더 간단한 상태 관리 패턴으로, 모델 클래스를 통해 상태를 관리하고 UI와 로직을 분리합니다. 지금은 Provider... 등 다른 라이브러리가 더 많이 사용되지만, 여전히 간단한 앱에서는 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;주요 특징&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Model &amp;amp; ScopedModel &amp;amp; ScopedModelDescendant의 세 가지 주요 클래스로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 데이터 변경 시, notifyListeners를 호출하여 관련 위젯만 다시 빌드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Provider보다 단순하지만 유연성은 다소 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]Redux&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741045607291&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';

// 1. 앱 상태 정의
class AppState {
  final int counter;
  
  AppState({required this.counter});
  
  AppState copyWith({int? counter}) {
    return AppState(counter: counter ?? this.counter);
  }
}

// 2. 액션 정의
class IncrementAction {}

// 3. 리듀서 정의
AppState reducer(AppState state, dynamic action) {
  if (action is IncrementAction) {
    return state.copyWith(counter: state.counter + 1);
  }
  return state;
}

// 4. 메인 앱
void main() {
  // 스토어 생성
  final store = Store&amp;lt;AppState&amp;gt;(
    reducer,
    initialState: AppState(counter: 0),
  );
  
  runApp(MyApp(store: store));
}

class MyApp extends StatelessWidget {
  final Store&amp;lt;AppState&amp;gt; store;
  
  const MyApp({Key? key, required this.store}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return StoreProvider&amp;lt;AppState&amp;gt;(
      store: store,
      child: MaterialApp(
        home: CounterPage(),
      ),
    );
  }
}

// 5. 카운터 페이지
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Redux 예시')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('버튼을 누른 횟수:'),
            // 상태 구독
            StoreConnector&amp;lt;AppState, String&amp;gt;(
              converter: (store) =&amp;gt; store.state.counter.toString(),
              builder: (context, count) {
                return Text(
                  count,
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: StoreConnector&amp;lt;AppState, VoidCallback&amp;gt;(
        // 디스패치 함수 변환
        converter: (store) {
          return () =&amp;gt; store.dispatch(IncrementAction());
        },
        builder: (context, callback) {
          return FloatingActionButton(
            onPressed: callback,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          );
        },
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux는 단방향 데이터 흐름을 가진 예측 가능한 상태 관리 라이브러리로, JavaScript 생태계에서 유래했습니다. 모든 상태 변화는 액션(Action)을 통해 이루어지며, 리듀서(Reducer)에서 현재 상태와 액션을 기반으로 새 상태를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;주요 개념&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Store: 앱의 모든 상태를 저장하는 단일 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Action: 상태 변경을 트기러하는 이벤트로, type과 payload를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Reducer: 현재 상태와 액션을 받아 새 상태를 반환하는 순수 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Middleware: 액션이 리듀서에 도달하기 전에 처리하는 함수로, 비동기 작업... 등에 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]MobX&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741045774679&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart';

// MobX 코드 생성을 위한 부분
part 'counter_store.g.dart';

// 1. Store 클래스 정의
class CounterStore = _CounterStore with _$CounterStore;

abstract class _CounterStore with Store {
  @observable
  int counter = 0;
  
  @computed
  String get counterString =&amp;gt; '현재 카운트: $counter';
  
  @action
  void increment() {
    counter++;
  }
}

// 2. 메인 앱
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // Store 인스턴스 생성
  final CounterStore counterStore = CounterStore();
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(counterStore: counterStore),
    );
  }
}

// 3. 카운터 페이지
class CounterPage extends StatelessWidget {
  final CounterStore counterStore;
  
  const CounterPage({Key? key, required this.counterStore}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('MobX 예시')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Observer 위젯으로 반응형 UI 구현
            Observer(
              builder: (_) =&amp;gt; Text(
                counterStore.counterString,
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counterStore.increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MobX는 관찰 가능한(Observable) 상태와 반응형 프로그래밍 패러다임을 사용하는 상태 관리 라이브러리입니다. 상태 변경을 자동으로 추적하고 관련 UI를 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;주요 개념&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Observable: 관찰할 수 있는 상태 값으로, 변경을 추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Action: 상태를 수정하는 함수로, &lt;span style=&quot;color: #006dd7;&quot;&gt;[5]트랜잭션&lt;/span&gt;으로 처리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Computed: 다른 Observable에서 파생된 값으로, 캐시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Observer: Observable 값의 변화에 반응하는 Widget입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[5]트랜잭션&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션은 '하나의 논리적 작업 단위'를 의미합니다. 이 작업 단위는 완전히 수행되거나 아예 수행되지 않아야 합니다. 중간 상태는 유효하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션을 일상생활로 비유하면 이해하기 쉽습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션은 &quot;모 아니면 도&quot;라는 원칙으로 작동하는 하나의 작업 묶음입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 은행 송금&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 내 계좌에서 10만원이 빠져나감&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 친구 계좌에 10만원이 들어감&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 두 단계를 반드시 함께 성공하거나 함께 실패해야 합니다. 만약 내 계좌에서만 돈이 빠져나가고 친구 계좌에 들어가지 않는다면, 돈이 증발하는 심각한 문제가 생깁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션은 위의 두 단계를 하나로 묶어서 &quot;전체가 성공하거나 아니면 아무 일도 없었던 것처럼&quot; 만들어 줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 온라인 쇼핑&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 상품 재고 감소&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 고객 돈 결제&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 배송 정보 등록&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 모든 과정이 트랜잭션으로 묶여 있어야 합니다. 결제만 되고 재고는 안 줄어들면, 상품 과잉 판매 문제가 발생합니다. 또한, 결제가 실패했는데 재고만 줄어들면, 판매자의 손해가 발생합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741046211286&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 트랜잭션 없이 상태 변경 (각각 UI 업데이트 발생)
userStore.name = &quot;홍길동&quot;;  // UI 업데이트 1번 발생
userStore.age = 30;        // UI 업데이트 2번 발생
userStore.isActive = true; // UI 업데이트 3번 발생

// 트랜잭션으로 묶은 상태 변경 (한 번의 UI 업데이트만 발생)
runInAction(() {
  userStore.name = &quot;홍길동&quot;;
  userStore.age = 30;
  userStore.isActive = true;
  // 이 모든 변경 후 UI 업데이트는 딱 1번만!
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[4]MobX&lt;/span&gt;에서 말한 트랜잭션은 여러 상태 변화를 하나로 묶어주는 것을 뜻합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Riverpod에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Riverpod는 Provider의 한계를 극복한 개선된 상태 관리 라이브러리로, 컴파일 타임 안전성 &amp;amp; 코드 재사용성 &amp;amp; 테스트 용이성을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ Provider의 한계 극복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Provider는 InheritedWidget의 한계로 인해 전역에서 Provider를 읽을 수 없고, 컴파일 타임에 오류를 잡기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Riverpod는 이러한 제약 없이 BuildContext 외부에서도 Provider에 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;주요 특징&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컴파일 타임 안정성: 타입 시스템을 활용해 컴파일 시점에서 많은 오류를 잡아냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Provider 재정의: 테스트와 개발 환경에서 Provider를 쉽게 오버라이드할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 자동 캐싱과 메모리 관리: 사용되지 않는 상태를 자동으로 폐기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 의존성 주입: 여러 Provider 간의 의존성을 쉽게 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 코드 공유: 여러 Provider 간에 상태와 로직을 쉽게 공유할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. BuildContext 없이도 Provider에 접근 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 용이성이 매우 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 의존성 오버라이드가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 자동 캐싱 및 메모리 관리가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 성능 최적화(선택적 재빌드)를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Provider에 비해 초기 학습 곡선이 다소 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 보일러플레이트 코드가 약간 더 많을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Riverpod의 주요 Provider 유형&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. Provider(기본 제공자)&lt;/p&gt;
&lt;pre id=&quot;code_1741046941959&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter_riverpod/flutter_riverpod.dart';

// 단순한 값 제공
final nameProvider = Provider&amp;lt;String&amp;gt;((ref) {
  return '홍길동';
});

// API 클라이언트와 같은 서비스 인스턴스 제공
final apiClientProvider = Provider&amp;lt;ApiClient&amp;gt;((ref) {
  return ApiClient('https://api.example.com');
});

// 사용 예시
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final name = ref.watch(nameProvider);
    return Text('이름: $name');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 단순한 값이나 객체를 제공하는 가장 기본적인 Provider입니다. 읽기 전용이며, 변경되지 않는 값에 적합합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. StateProvider(간단한 상태 제공자)&lt;/p&gt;
&lt;pre id=&quot;code_1741047029910&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카운터 상태 관리
final counterProvider = StateProvider&amp;lt;int&amp;gt;((ref) {
  return 0; // 초기값
});

// 드롭다운 선택 값 관리
final selectedCategoryProvider = StateProvider&amp;lt;String&amp;gt;((ref) {
  return '전체'; // 기본 선택값
});

// 사용 예시
class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    
    return Column(
      children: [
        Text('카운터: ${counter.value}'),
        ElevatedButton(
          onPressed: () =&amp;gt; ref.read(counterProvider.notifier).state++,
          child: Text('증가'),
        ),
        ElevatedButton(
          onPressed: () =&amp;gt; ref.read(counterProvider.notifier).state = 0,
          child: Text('초기화'),
        ),
      ],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 간단한 상태를 관리하는데 사용됩니다. 상태를 직접 변경할 수 있어서 간단한 상황에 적합합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. StateNotifierProvider(복잡한 상태 제공자)&lt;/p&gt;
&lt;pre id=&quot;code_1741047149256&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 할 일 항목 클래스
class Todo {
  final String id;
  final String title;
  final bool completed;

  Todo({
    required this.id,
    required this.title,
    this.completed = false,
  });

  Todo copyWith({String? title, bool? completed}) {
    return Todo(
      id: this.id,
      title: title ?? this.title,
      completed: completed ?? this.completed,
    );
  }
}

// 할 일 목록 상태 관리자
class TodosNotifier extends StateNotifier&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; {
  TodosNotifier() : super([]);  // 초기값: 빈 배열

  // 할 일 추가
  void addTodo(String title) {
    state = [
      ...state,
      Todo(id: DateTime.now().toString(), title: title),
    ];
  }

  // 할 일 완료 상태 토글
  void toggleTodo(String id) {
    state = state.map((todo) {
      if (todo.id == id) {
        return todo.copyWith(completed: !todo.completed);
      }
      return todo;
    }).toList();
  }

  // 할 일 삭제
  void removeTodo(String id) {
    state = state.where((todo) =&amp;gt; todo.id != id).toList();
  }
}

// StateNotifierProvider 정의
final todosProvider = StateNotifierProvider&amp;lt;TodosNotifier, List&amp;lt;Todo&amp;gt;&amp;gt;((ref) {
  return TodosNotifier();
});

// 사용 예시
class TodoListScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todosProvider);
    
    return Scaffold(
      appBar: AppBar(title: Text('할 일 목록')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          final todo = todos[index];
          return ListTile(
            title: Text(todo.title),
            leading: Checkbox(
              value: todo.completed,
              onChanged: (_) =&amp;gt; ref.read(todosProvider.notifier).toggleTodo(todo.id),
            ),
            trailing: IconButton(
              icon: Icon(Icons.delete),
              onPressed: () =&amp;gt; ref.read(todosProvider.notifier).removeTodo(todo.id),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 간단한 예시이므로 실제로는 대화상자를 표시하여 제목 입력 받기
          ref.read(todosProvider.notifier).addTodo('새 할 일');
        },
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 복잡한 상태를 관리하기 위한 Provider로, StateNotifier 클래스와 함께 사용됩니다. 상태 변경 로직을 캡슐화하는데 적합합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. FutureProvider(비동기 데이터 제공자)&lt;/p&gt;
&lt;pre id=&quot;code_1741047277193&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사용자 데이터 모델
class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

// 사용자 API 서비스
class UserService {
  Future&amp;lt;User&amp;gt; fetchUser(int id) async {
    // 실제로는 HTTP 요청을 보냄
    await Future.delayed(Duration(seconds: 2)); // 네트워크 지연 시뮬레이션
    
    // 예시 데이터
    return User.fromJson({
      'id': id,
      'name': '홍길동',
      'email': 'hong@example.com',
    });
  }
}

// API 서비스 Provider
final userServiceProvider = Provider&amp;lt;UserService&amp;gt;((ref) {
  return UserService();
});

// 사용자 데이터를 가져오는 FutureProvider
final userProvider = FutureProvider.family&amp;lt;User, int&amp;gt;((ref, userId) async {
  final userService = ref.read(userServiceProvider);
  return userService.fetchUser(userId);
});

// 사용 예시
class UserProfileScreen extends ConsumerWidget {
  final int userId;
  
  UserProfileScreen({required this.userId});
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider(userId));
    
    return Scaffold(
      appBar: AppBar(title: Text('사용자 프로필')),
      body: userAsync.when(
        loading: () =&amp;gt; Center(child: CircularProgressIndicator()),
        error: (error, stack) =&amp;gt; Center(child: Text('오류 발생: $error')),
        data: (user) =&amp;gt; Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('이름: ${user.name}', style: TextStyle(fontSize: 24)),
            SizedBox(height: 8),
            Text('이메일: ${user.email}'),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 비동기 작업의 결과를 제공하는 Provider입니다. API 호출이나 데이터베이스 쿼리에 적합합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5. StreamProvider(스트림 데이터 제공자)&lt;/p&gt;
&lt;pre id=&quot;code_1741047354968&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메시지 모델
class Message {
  final String id;
  final String text;
  final String sender;
  final DateTime timestamp;

  Message({
    required this.id,
    required this.text,
    required this.sender,
    required this.timestamp,
  });
}

// 채팅 서비스
class ChatService {
  // 실제로는 Firebase 같은 서비스와 연결
  Stream&amp;lt;List&amp;lt;Message&amp;gt;&amp;gt; getMessages(String chatId) {
    // 예시: 메시지 스트림 시뮬레이션
    return Stream.periodic(Duration(seconds: 1), (count) {
      // 새 메시지 추가 시뮬레이션
      return List.generate(count + 1, (index) {
        return Message(
          id: 'msg_$index',
          text: '메시지 $index',
          sender: index % 2 == 0 ? '나' : '상대방',
          timestamp: DateTime.now().subtract(Duration(minutes: index)),
        );
      });
    }).take(10); // 예시를 위해 10개로 제한
  }
}

// 채팅 서비스 Provider
final chatServiceProvider = Provider&amp;lt;ChatService&amp;gt;((ref) {
  return ChatService();
});

// 메시지 StreamProvider
final messagesProvider = StreamProvider.family&amp;lt;List&amp;lt;Message&amp;gt;, String&amp;gt;((ref, chatId) {
  final chatService = ref.read(chatServiceProvider);
  return chatService.getMessages(chatId);
});

// 사용 예시
class ChatScreen extends ConsumerWidget {
  final String chatId = 'chat_123';
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final messagesAsync = ref.watch(messagesProvider(chatId));
    
    return Scaffold(
      appBar: AppBar(title: Text('채팅')),
      body: messagesAsync.when(
        loading: () =&amp;gt; Center(child: CircularProgressIndicator()),
        error: (error, stack) =&amp;gt; Center(child: Text('오류 발생: $error')),
        data: (messages) =&amp;gt; ListView.builder(
          reverse: true,
          itemCount: messages.length,
          itemBuilder: (context, index) {
            final message = messages[index];
            return ListTile(
              title: Text(message.text),
              subtitle: Text(message.sender),
              trailing: Text(
                '${message.timestamp.hour}:${message.timestamp.minute}',
              ),
            );
          },
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● Stream 데이터를 제공하는 Provider입니다. 실시간 업데이트나 이벤트 스트림 관리에 적합합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6. ChangeNotifierProvider(ChangeNotifier 기반 제공자)&lt;/p&gt;
&lt;pre id=&quot;code_1741047435656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/foundation.dart';

// ChangeNotifier를 사용한 카트 모델
class CartModel extends ChangeNotifier {
  List&amp;lt;Product&amp;gt; _items = [];
  
  List&amp;lt;Product&amp;gt; get items =&amp;gt; _items;
  
  double get totalPrice =&amp;gt; _items.fold(0, (sum, item) =&amp;gt; sum + item.price);
  
  void addProduct(Product product) {
    _items.add(product);
    notifyListeners();
  }
  
  void removeProduct(Product product) {
    _items.remove(product);
    notifyListeners();
  }
  
  void clearCart() {
    _items = [];
    notifyListeners();
  }
}

class Product {
  final String id;
  final String name;
  final double price;
  
  Product({required this.id, required this.name, required this.price});
}

// ChangeNotifierProvider 정의
final cartProvider = ChangeNotifierProvider&amp;lt;CartModel&amp;gt;((ref) {
  return CartModel();
});

// 사용 예시
class CartScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cart = ref.watch(cartProvider);
    
    return Scaffold(
      appBar: AppBar(
        title: Text('장바구니'),
        actions: [
          IconButton(
            icon: Icon(Icons.delete),
            onPressed: () =&amp;gt; ref.read(cartProvider).clearCart(),
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: cart.items.length,
              itemBuilder: (context, index) {
                final product = cart.items[index];
                return ListTile(
                  title: Text(product.name),
                  subtitle: Text('₩${product.price}'),
                  trailing: IconButton(
                    icon: Icon(Icons.remove_circle),
                    onPressed: () =&amp;gt; ref.read(cartProvider).removeProduct(product),
                  ),
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('총액:', style: TextStyle(fontSize: 18)),
                Text('₩${cart.totalPrice}', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              ],
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 예시용 상품 추가
          ref.read(cartProvider).addProduct(
            Product(id: DateTime.now().toString(), name: '테스트 상품', price: 10000),
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● Flutter의 ChangeNotifier를 사용하여 상태를 관리하는 Provider입니다. Provider 패키지에서 마이그레이션 하는 경우에 유용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 Provider들을 조합하여 복잡한 애플리케이션 상태를 효율적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Riverpod의 강점은 이러한 Provider들을 서로 결합하고 참조할 수 있다는 점입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741047542423&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 인증 상태 관리
final authProvider = StateNotifierProvider&amp;lt;AuthNotifier, AuthState&amp;gt;((ref) {
  return AuthNotifier();
});

// 현재 사용자 정보 - authProvider에 의존
final currentUserProvider = FutureProvider&amp;lt;User?&amp;gt;((ref) async {
  final authState = ref.watch(authProvider);
  
  // 로그인 상태에 따라 사용자 정보 제공
  if (authState is AuthAuthenticated) {
    final userService = ref.read(userServiceProvider);
    return userService.fetchUser(authState.userId);
  }
  
  return null; // 로그인되지 않은 경우
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Riverpod는 다양한 종류의 Provider를 제공하며, 이들을 조합하여 앱의 상태를 체계적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Bloc에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Bloc(Business Logic Component)은 이벤트와 상태를 명확히 분리하고 Stream을 활용하여 예측 가능한 상태 관리를 제공하는 아키텍처 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 기본 원리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 관심사 분리: UI와 비즈니스 로직을 명확하게 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 단방향 데이터 흐름: 이벤트 =&amp;gt; Bloc =&amp;gt; 상태의 일관된 흐름을 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 반응형 프로그래밍: Stream과 StreamBuilder를 활용하여 상태 변화에 반응합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ Bloc의 주요 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 이벤트(Event): 사용자 액션이나 시스템 이벤트를 나타내는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 상태(State): UI의 현재 상태를 나타내는 불변(immutable) 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Bloc 클래스: 이벤트를 받아 처리하고 새로운 상태를 생성하는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 작동 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● UI에서 이벤트가 발생하면 Bloc에 이벤트를 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Bloc는 이벤트를 처리하고 필요한 비즈니스 로직을 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 처리 결과에 따라 새로운 상태(State)를 생성하여 Stream으로 내보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● UI는 상태 Stream을 구독하고 있다가 새로운 상태가 오면 화면을 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테스트 용이성이 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코드 구조화와 유지 관리가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 비즈니스 로직과 UI의 명확한 분리를 유도합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 디버깅과 상태 추적이 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 예측 가능한 단방향 데이터 흐름을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 작은 앱에는 과도한 보일러플레이트 코드가 생길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 초기 학습 곡선이 다소 가파를 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Stream 기반 프로그래밍에 익숙해져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;간단한 카운터 앱 Bloc 예제&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741048008592&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카운터 Bloc

// counter_event.dart - 이벤트 정의
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {}

// counter_state.dart - 상태 정의
class CounterState {
  final int count;
  
  CounterState(this.count);
}

// counter_bloc.dart - BLoC 구현
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterBloc extends Bloc&amp;lt;CounterEvent, CounterState&amp;gt; {
  CounterBloc() : super(CounterState(0)) {
    // 이벤트 핸들러 등록
    on&amp;lt;IncrementEvent&amp;gt;((event, emit) {
      emit(CounterState(state.count + 1));
    });
    
    on&amp;lt;DecrementEvent&amp;gt;((event, emit) {
      emit(CounterState(state.count - 1));
    });
    
    on&amp;lt;ResetEvent&amp;gt;((event, emit) {
      emit(CounterState(0));
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741048053903&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카운터 UI

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) =&amp;gt; CounterBloc(),
      child: CounterView(),
    );
  }
}

class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('BLoC 카운터 예제')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('카운터 값:'),
            // BlocBuilder로 상태 변화 감지 및 UI 업데이트
            BlocBuilder&amp;lt;CounterBloc, CounterState&amp;gt;(
              builder: (context, state) {
                return Text(
                  '${state.count}',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                );
              },
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    // BLoC에 이벤트 전달
                    context.read&amp;lt;CounterBloc&amp;gt;().add(DecrementEvent());
                  },
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    context.read&amp;lt;CounterBloc&amp;gt;().add(ResetEvent());
                  },
                  child: Text('초기화'),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    context.read&amp;lt;CounterBloc&amp;gt;().add(IncrementEvent());
                  },
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Provider에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Provider는 InheritedWidget을 기반으로 한 간단하면서도 효과적인 상태 관리 및 의존성 주입 라이브러리로, 위젯 트리를 통해 데이터를 효율적으로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 기본 개념과 원리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● InheritedWidget을 기반으로 하지만 훨씬 사용하기 쉽고 강력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯 트리를 통해 데이터를 효율적으로 전달하는 메커니즘을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 의존성 주입 패턴을 적용하여 앱의 다양한 부분에서 데이터에 쉽게 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 주요 Provider 유형 (위의 4번 'Riverpod'에서 자세히 설명했기에 간단한 설명만 하겠습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Provider: 가장 기본적인 형태로, 단순한 값 제공 (읽기 전용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● ChangeNotifierProvider: ChangeNotifier를 사용하여 변경 가능한 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● FutureProvider: 비동기 작업 결과 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● StreamProvider: Stream 데이터 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● MultiProvider: 여러 Provider를 한 번에 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● ProxyProvider: 다른 Provider에 의존하는 Provider 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 코드의 가독성과 유지 관리성이 향상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 불필요한 위젯 재빌드 방지로 성능을 최적화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 비즈니스 로직과 UI 코드를 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 테스트 용이성이 향상됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 복잡한 상태 관리에는 기능이 제한적일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. BuildContext가 필요하여 모든 곳에서 접근하기 어렵습니다. (Riverpod가 이 문제를 해결한 라이브러리입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 깊은 중첩 구조에서는 가독성이 떨어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7. &lt;span style=&quot;color: #8a3db6;&quot;&gt;GetX에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ GetX는 상태 관리 &amp;amp; 라우팅 &amp;amp; 종속성 주입을 통합적으로 제공하는 초경량 솔루션으로, 최소한의 코드로 반응형 애플리케이션을 구축할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 세 가지 핵심 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 상태 관리: 간단하고 반응형 상태 관리를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 라우팅 관리: 네비게이션과 라우팅을 위한 포괄적인 솔루션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 종속성 관리: 효율적인 의존성 주입 시스템을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ GetX 상태 관리의 특징&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 단순 상태 관리: GetBuilder를 사용하여 간단한 상태 관리를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 반응형 상태 관리: Rx변수와 .obs확장을 통한 반응형 프로그래밍을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● GetX Controller: 비즈니스 로직을 캡슐화하는 컨트롤러 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 메모리 관리: 사용하지 않는 컨트롤러를 자동으로 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ GetX 라우팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 화면 전환을 위한 간결한 구문 Get.to &amp;amp; Get.back... 등을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 명명된 라우트 지원 및 중첩 네비게이션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 미들웨어 &amp;amp; 전환 애니메이션 &amp;amp; 페이지 간 데이터 전달을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● BuildContext 없이도 네비게이션을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 종속성 주입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Get.put &amp;amp; Get.lazyPut &amp;amp; Get.find 메서드를 통한 간단한 의존성을 주입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 싱글톤 &amp;amp; 팩토리 &amp;amp; 일회성 인스턴스 관리를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 바인딩을 통한 자동 의존성 관리를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 코드 간결성: 보일러플레이트 코드를 최소화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 성능 최적화: 필요한 위젯만 재빌드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● BuildContext에 의존하지 않습니다. 어디에서나 접근 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 통합 솔루션을 제공합니다. 별도의 라이브러리 없이도 모든 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 직관적인 API와 쉬운 학습 곡선을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 전역 싱글톤을 많이 사용해서 테스트가 어려울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 남용 시, 앱 구조가 복잡해질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 지나친 간결함으로 인해 디버깅이 어려울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;간단한 GetX 사용 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741049338687&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러 정의
class CounterController extends GetxController {
  var count = 0.obs;  // 반응형 변수

  void increment() {
    count++;  // 자동으로 UI 업데이트
  }
}

// GetX 앱 설정
void main() {
  runApp(
    GetMaterialApp(  // MaterialApp 대신 GetMaterialApp 사용
      home: Home(),
    ),
  );
}

// 컨트롤러 사용
class Home extends StatelessWidget {
  final CounterController controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('GetX 예제')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 반응형 UI
            Obx(() =&amp;gt; Text('${controller.count}')),
            ElevatedButton(
              onPressed: controller.increment,
              child: Text('증가'),
            ),
            // 페이지 이동
            ElevatedButton(
              onPressed: () =&amp;gt; Get.to(SecondPage()),
              child: Text('다음 페이지'),
            ),
          ],
        ),
      ),
    );
  }
}

// 스낵바, 다이얼로그 등의 유틸리티
void showMessage() {
  Get.snackbar(
    '알림',
    '메시지가 도착했습니다.',
    snackPosition: SnackPosition.BOTTOM,
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;8. &lt;span style=&quot;color: #8a3db6;&quot;&gt;위젯 트리 구조에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter의 위젯 트리는 계층적 구조로 UI를 구성하며, 모든 UI 요소는 위젯 트리 내에서 부모-자식 관계를 형성하여 효율적인 렌더링과 재사용성을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 UI는 위젯 트리라고 불리는 계층적 구조로 구성됩니다. 이 트리는 루트 위젯에서 시작하여 자식 위젯들로 분기되는 형태입니다. 각 위젯은 하나의 부모와 여러 자식을 가질 수 있으며, 이는 UI 컴포넌트의 구성과 배치를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 위젯 트리는 세 가지 주요 트리로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]위젯 트리&lt;/span&gt;: UI의 구성을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]엘리먼트 트리&lt;/span&gt;: 위젯의 인스턴스를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&lt;span style=&quot;color: #006dd7;&quot;&gt; [3]렌더 트리&lt;/span&gt;: 실제 화면에 그려질 요소들을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]렌더링 파이프라인&lt;/span&gt;은 build &amp;amp; layout &amp;amp; paint라는 세 단계로 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● build 단계에서 위젯 트리가 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● layout 단계에서 각 요소의 크기와 위치가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● paint 단계에서 실제 픽셀이 화면에 그려집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯 트리 구조는 코드의 재사용성과 &lt;span style=&quot;color: #006dd7;&quot;&gt;[5]모듈화&lt;/span&gt;를 촉진하며, setState 호출 시, 효율적인 부분 업데이트를 가능하게 합니다. 또한, 필요한 위젯만 다시 빌드하므로 성능이 최적화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]위젯트리&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741055113522&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 위젯 트리 생성 부분
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('트리 구조 예제'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('이것은 Text 위젯입니다'),
              SizedBox(height: 20),
              Container(
                width: 200,
                height: 100,
                color: Colors.blue,
                child: Center(
                  child: Text(
                    '컨테이너 안의 텍스트',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  print('버튼이 눌렸습니다');
                },
                child: Text('버튼'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯 트리는 Flutter UI의 구조를 정의하는 불변(immutable) 객체들의 계층입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯은 UI의 설계도(blueprint)로 생각할 수 있으며, 어떻게 UI가 보여야 하는지를 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 상태가 변경될 때마다 새로운 위젯 트리가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;span style=&quot;color: #006dd7;&quot;&gt; [2]엘리먼트 트리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 엘리먼트 트리는 위젯 트리의 실제 인스턴스를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 각 위젯에 대응하는 엘리먼트가 있으며, 위젯의 수명 주기를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 엘리먼트는 위젯과 렌더 객체 사이의 중간 매개체 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 상태가 변경될 때, Flutter는 이전 엘리먼트 트리와 새 위젯 트리를 비교하여 최소한의 변경만 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]렌더 트리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 렌더 트리는 화면에 실제로 그려질 객체들을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● RenderObject들로 구성되며, 레이아웃 계산과 실제 페인팅을 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 모든 위젯이 RenderObject를 생성하는 것은 아닙니다. RenderObjectWidget(ex. Container &amp;amp; Text... 등)만 렌더 객체를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]렌더링 파이프라인&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741055216113&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; 

void main() {
  // 렌더링 파이프라인 디버깅 활성화
  debugPaintSizeEnabled = true; // 레이아웃 단계 시각화
  debugPaintBaselinesEnabled = true; // 텍스트 기준선 표시
  debugPaintLayerBordersEnabled = true; // 레이어 경계 표시
  
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() =&amp;gt; _MyAppState();
}

class _MyAppState extends State&amp;lt;MyApp&amp;gt; {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    print('Build 메서드 호출됨'); // Build 단계
    
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('렌더링 파이프라인 예제')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 애니메이션으로 레이아웃 변경 보여주기
              AnimatedContainer(
                duration: Duration(seconds: 1),
                width: _expanded ? 300.0 : 200.0,
                height: _expanded ? 200.0 : 100.0,
                color: _expanded ? Colors.blue : Colors.red,
                // Layout 및 Paint 단계를 볼 수 있는 커스텀 위젯
                child: CustomPaint(
                  painter: MyCustomPainter(),
                  child: Center(
                    child: Text(
                      '터치하세요',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
              SizedBox(height: 20),
              Text('상태: ${_expanded ? &quot;확장됨&quot; : &quot;축소됨&quot;}'),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _expanded = !_expanded; // 상태 변경으로 rebuild 트리거
            });
          },
          child: Icon(_expanded ? Icons.remove : Icons.add),
        ),
      ),
    );
  }
}

// Paint 단계를 시연하는 CustomPainter
class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    print('Paint 메서드 호출됨'); // Paint 단계
    
    final paint = Paint()
      ..color = Colors.yellow.withOpacity(0.3)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 5.0;
    
    // 사각형 그리기
    canvas.drawRect(
      Rect.fromLTWH(10, 10, size.width - 20, size.height - 20),
      paint,
    );
    
    // 대각선 그리기
    canvas.drawLine(
      Offset(0, 0),
      Offset(size.width, size.height),
      paint..color = Colors.green.withOpacity(0.5),
    );
    
    canvas.drawLine(
      Offset(size.width, 0),
      Offset(0, size.height),
      paint,
    );
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) =&amp;gt; true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Build 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 위젯의 build 메서드가 호출되어 위젯 트리를 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 이 단계에서 UI의 구조와 속성이 정의됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 상태가 변경되면 setState를 통해 이 단계가 다시 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Layout 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 각 렌더 객체의 크기와 위치를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 이 과정은 두 단계로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1. 부모에서 자식으로 제약 조건(constraints)을 전달(top-down)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2. 자식에서 부모로 크기 정보를 전달(bottom-up)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 이 과정을 통해 각 요소의 정확한 크기와 위치가 계산됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Paint 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 실제 픽셀을 화면에 그리는 단계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 렌더 객체들은 paint 메서드를 통해 Canvas에 자신을 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ● 이 단계에서 그림자 &amp;amp; 효과 &amp;amp; 클리핑... 등이 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 파이프라인 구조는 Flutter가 60fps의 부드러운 성능을 달성하는 데에 중요한 역할을 합니다. 상태 변경 시에 필요한 부분만 다시 빌드하고 레이아웃을 계산하여 효율적으로 화면을 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[5]모듈화&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741055552396&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;
  final Color color;

  const CustomButton({
    Key? key,
    required this.text,
    required this.onPressed,
    this.color = Colors.blue,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
        ),
      ),
      child: Text(text),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 모듈화란 UI와 로직을 재사용 가능하고 관리하기 쉬운 작은 단위로 분리하는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯 분리: 복잡한 UI를 작고 독립적인 위젯으로 분리하여 코드의 가독성과 유지보수성을 향상시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 재사용성: 공통 위젯을 별도의 클래스로 분리하여 여러 화면에서 재사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 관심사 분리: UI 위젯 &amp;amp; 비즈니스 로직 &amp;amp; 데이터 모델을 명확히 분리하여 코드 구조를 개선합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 패키지화: 관련 기능을 패키지로 묶어 프로젝트 간에 공유하거나 pub.dev에 배포할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시는 앱 전체에서 사용되는 공통 버튼을 모듈화한 것입니다. 이렇게 모듈화된 위젯은 앱 전체에서 일관된 디자인을 유지하고, 변경 사항을 한 곳에서 관리할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;9. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter에서 비동기 프로그래밍을 하는 방법에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter에서 Future &amp;amp; async/await &amp;amp; Stream을 활용하여 비동기 작업을 효율적으로 처리할 수 있으며, 무거운 연산은 Isolate를 통해 별도 스레드에서 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Future&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056007971&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureExample(),
    );
  }
}

class FutureExample extends StatefulWidget {
  @override
  _FutureExampleState createState() =&amp;gt; _FutureExampleState();
}

class _FutureExampleState extends State&amp;lt;FutureExample&amp;gt; {
  String _result = &quot;결과가 여기에 표시됩니다&quot;;

  // Future를 반환하는 함수
  Future&amp;lt;String&amp;gt; fetchData() async {
    // 네트워크 요청을 시뮬레이션하는 지연
    await Future.delayed(Duration(seconds: 2));
    return &quot;데이터 가져오기 성공!&quot;;
  }

  // then()을 사용한 Future 처리
  void _loadDataWithThen() {
    setState(() =&amp;gt; _result = &quot;로딩 중...&quot;);
    fetchData().then((value) {
      setState(() =&amp;gt; _result = value);
    }).catchError((error) {
      setState(() =&amp;gt; _result = &quot;오류 발생: $error&quot;);
    });
  }

  // async/await를 사용한 Future 처리
  void _loadDataWithAsync() async {
    setState(() =&amp;gt; _result = &quot;로딩 중...&quot;);
    try {
      final value = await fetchData();
      setState(() =&amp;gt; _result = value);
    } catch (error) {
      setState(() =&amp;gt; _result = &quot;오류 발생: $error&quot;);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Future 예제')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_result, style: TextStyle(fontSize: 18)),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _loadDataWithThen,
              child: Text('then()으로 데이터 로드'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: _loadDataWithAsync,
              child: Text('async/await로 데이터 로드'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Future는 미래의 어느 시점에 완료될 단일 비동기 작업을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;async/await&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741055841979&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Future&amp;lt;String&amp;gt; fetchData() async {
  // 비동기 작업
  return await http.get('https://api.example.com/data');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt; &lt;/span&gt;&lt;/span&gt;async/await 문법을 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있어서 가독성이 향상됩니다. 그래서 Future.then을 사용한 콜백 방식보다 async/await를 사용하는 것이 권장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;FutureBuilder&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056067470&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureBuilderExample(),
    );
  }
}

class FutureBuilderExample extends StatelessWidget {
  // Future를 반환하는 함수
  Future&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; fetchItems() async {
    await Future.delayed(Duration(seconds: 2));
    return [&quot;항목 1&quot;, &quot;항목 2&quot;, &quot;항목 3&quot;, &quot;항목 4&quot;, &quot;항목 5&quot;];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('FutureBuilder 예제')),
      body: Center(
        child: FutureBuilder&amp;lt;List&amp;lt;String&amp;gt;&amp;gt;(
          future: fetchItems(), // Future 제공
          builder: (context, snapshot) {
            // 상태에 따른 UI 처리
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator(); // 로딩 중
            } else if (snapshot.hasError) {
              return Text(&quot;오류 발생: ${snapshot.error}&quot;); // 오류 발생
            } else if (snapshot.hasData) {
              // 데이터 로드 성공
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(snapshot.data![index]),
                  );
                },
              );
            } else {
              return Text(&quot;데이터 없음&quot;); // 데이터가 없는 경우
            }
          },
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Flutter는 FutureBuilder 위젯을 제공하여 Future의 상태(대기 &amp;amp; 완료 &amp;amp; 오류)에 따라 UI를 쉽게 업데이트할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Stream&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056111313&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'dart:async';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StreamExample(),
    );
  }
}

class StreamExample extends StatefulWidget {
  @override
  _StreamExampleState createState() =&amp;gt; _StreamExampleState();
}

class _StreamExampleState extends State&amp;lt;StreamExample&amp;gt; {
  // 스트림 컨트롤러 생성
  final _streamController = StreamController&amp;lt;int&amp;gt;();
  int _counter = 0;
  late Timer _timer;

  @override
  void initState() {
    super.initState();
    // 1초마다 카운터 증가하고 스트림에 추가
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      _counter++;
      _streamController.sink.add(_counter);
    });
  }

  @override
  void dispose() {
    // 리소스 정리
    _timer.cancel();
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream 예제')),
      body: Center(
        child: StreamBuilder&amp;lt;int&amp;gt;(
          stream: _streamController.stream,
          initialData: 0,
          builder: (context, snapshot) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  '현재 카운트:',
                  style: TextStyle(fontSize: 18),
                ),
                Text(
                  '${snapshot.data}',
                  style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Stream은 시간에 따라 여러 값을 비동기적으로 전달하는 데이터 시퀀스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;StreamBuilder&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056127828&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'dart:async';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StreamBuilderExample(),
    );
  }
}

class StreamBuilderExample extends StatefulWidget {
  @override
  _StreamBuilderExampleState createState() =&amp;gt; _StreamBuilderExampleState();
}

class _StreamBuilderExampleState extends State&amp;lt;StreamBuilderExample&amp;gt; {
  // 랜덤 색상을 생성하는 스트림
  Stream&amp;lt;Color&amp;gt; colorStream() async* {
    final List&amp;lt;Color&amp;gt; colors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.yellow,
      Colors.purple,
      Colors.orange,
    ];
    
    var random = DateTime.now().millisecondsSinceEpoch;
    
    while (true) {
      await Future.delayed(Duration(seconds: 1));
      random = (random * 1664525 + 1013904223) % 0xFFFFFFFF;
      yield colors[random % colors.length];
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('StreamBuilder 예제')),
      body: Center(
        child: StreamBuilder&amp;lt;Color&amp;gt;(
          stream: colorStream(),
          builder: (context, snapshot) {
            return AnimatedContainer(
              duration: Duration(milliseconds: 500),
              width: 200,
              height: 200,
              color: snapshot.data ?? Colors.grey,
              child: Center(
                child: Text(
                  '색상 변경 중...',
                  style: TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● StreamBuilder 위젯을 사용하면 스트림 데이터에 반응하는 UI를 쉽게 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Completer&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056153401&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'dart:async';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CompleterExample(),
    );
  }
}

class CompleterExample extends StatefulWidget {
  @override
  _CompleterExampleState createState() =&amp;gt; _CompleterExampleState();
}

class _CompleterExampleState extends State&amp;lt;CompleterExample&amp;gt; {
  String _result = &quot;결과가 여기에 표시됩니다&quot;;
  
  // Completer를 사용해 수동으로 Future 완료하기
  Future&amp;lt;String&amp;gt; performAsyncOperation() {
    final completer = Completer&amp;lt;String&amp;gt;();
    
    // 비동기 작업 시뮬레이션
    Future.delayed(Duration(seconds: 2), () {
      // 작업 성공 시 completer 완료
      if (DateTime.now().second % 2 == 0) {
        completer.complete(&quot;작업이 성공적으로 완료되었습니다!&quot;);
      } else {
        // 작업 실패 시 예외 발생
        completer.completeError(&quot;작업 중 오류가 발생했습니다&quot;);
      }
    });
    
    // Future를 즉시 반환
    return completer.future;
  }
  
  void _startOperation() async {
    setState(() =&amp;gt; _result = &quot;작업 진행 중...&quot;);
    
    try {
      final result = await performAsyncOperation();
      setState(() =&amp;gt; _result = result);
    } catch (error) {
      setState(() =&amp;gt; _result = &quot;$error&quot;);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Completer 예제')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_result, style: TextStyle(fontSize: 18)),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _startOperation,
              child: Text('비동기 작업 시작'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Completer는 수동으로 Future를 생성하고 완료하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Isolate&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056181299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'dart:isolate';
import 'dart:math';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: IsolateExample(),
    );
  }
}

class IsolateExample extends StatefulWidget {
  @override
  _IsolateExampleState createState() =&amp;gt; _IsolateExampleState();
}

class _IsolateExampleState extends State&amp;lt;IsolateExample&amp;gt; {
  String _result = &quot;결과가 여기에 표시됩니다&quot;;
  bool _isCalculating = false;

  // Isolate에서 피보나치 수열 계산
  Future&amp;lt;void&amp;gt; calculateFibonacciWithIsolate(int n) async {
    setState(() {
      _isCalculating = true;
      _result = &quot;계산 중...&quot;;
    });

    // Isolate 통신을 위한 포트 생성
    final receivePort = ReceivePort();

    // Isolate 시작
    await Isolate.spawn(fibonacciCalculator, [receivePort.sendPort, n]);

    // 결과 수신
    final response = await receivePort.first;
    
    setState(() {
      _result = &quot;피보나치($n) = $response&quot;;
      _isCalculating = false;
    });
  }

  // 메인 UI 스레드에서 직접 계산
  void calculateFibonacciDirectly(int n) {
    setState(() {
      _isCalculating = true;
      _result = &quot;계산 중...&quot;;
    });

    // 의도적으로 UI 블로킹 작업 시뮬레이션
    final result = fibonacci(n);
    
    setState(() {
      _result = &quot;피보나치($n) = $result&quot;;
      _isCalculating = false;
    });
  }

  // 재귀적 피보나치 계산 (의도적으로 비효율적)
  static int fibonacci(int n) {
    if (n &amp;lt;= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
  }

  // Isolate에서 실행될 함수
  static void fibonacciCalculator(List&amp;lt;dynamic&amp;gt; params) {
    final SendPort sendPort = params[0];
    final int n = params[1];
    
    final result = fibonacci(n);
    sendPort.send(result);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Isolate 예제')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_result, style: TextStyle(fontSize: 18)),
            SizedBox(height: 20),
            if (_isCalculating)
              CircularProgressIndicator()
            else
              Column(
                children: [
                  ElevatedButton(
                    onPressed: () =&amp;gt; calculateFibonacciWithIsolate(35),
                    child: Text('Isolate로 계산 (n=35)'),
                  ),
                  SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: () =&amp;gt; calculateFibonacciDirectly(35),
                    child: Text('UI 스레드에서 계산 (n=35) - 주의: UI 멈춤'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.red,
                    ),
                  ),
                ],
              ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Isolate는 메인 UI 스레드를 차단하지 않고 무거운 연산을 별도의 스레드에서 실행할 수 있게 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제들은 Flutter에서 비동기 프로그래밍의 다양한 접근 방식을 보여주기 위해서 작성했습니다. Future와 async/await은 단일 비동기 작업을, Stream은 시간에 따른 데이터 흐름을, FutureBuilder &amp;amp; StreamBuilder는 비동기 데이터에 따른 UI 업데이트를, Completer는 수동 Future 완료를, Isolate는 무거운 계산 작업의 백그라운드 처리를 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;10. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter에서 API 호출을 하는 방법에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter에서는 http &amp;amp; dio 같은 라이브러리를 사용하여 Restful API를 호출하고, JSON 데이터를 모델 객체로 변환하여 앱에서 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;http 라이브러리&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056602554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:http/http.dart' as http;

Future&amp;lt;void&amp;gt; fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com/data'));
  if (response.statusCode == 200) {
    // 성공적으로 데이터를 받아옴
    final data = jsonDecode(response.body);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http 라이브러리는 Flutter 팀에서 제공하는 기본적인 HTTP 클라이언트로, 간단한 API 호출에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;dio 라이브러리&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741056753319&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dio 예제',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: DioExample(),
    );
  }
}

class DioExample extends StatefulWidget {
  @override
  _DioExampleState createState() =&amp;gt; _DioExampleState();
}

class _DioExampleState extends State&amp;lt;DioExample&amp;gt; {
  final Dio _dio = Dio();
  String _responseData = '여기에 데이터가 표시됩니다';
  bool _isLoading = false;

  // GET 요청 예제
  Future&amp;lt;void&amp;gt; _fetchData() async {
    setState(() {
      _isLoading = true;
      _responseData = '로딩 중...';
    });

    try {
      // 기본 GET 요청
      final response = await _dio.get('https://jsonplaceholder.typicode.com/posts/1');
      
      setState(() {
        _responseData = 'GET 응답:\n${response.data.toString()}';
      });
    } catch (e) {
      setState(() {
        _responseData = '오류 발생: ${e.toString()}';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  // POST 요청 예제
  Future&amp;lt;void&amp;gt; _postData() async {
    setState(() {
      _isLoading = true;
      _responseData = '로딩 중...';
    });

    try {
      // 헤더 및 데이터와 함께 POST 요청
      final response = await _dio.post(
        'https://jsonplaceholder.typicode.com/posts',
        data: {
          'title': 'foo',
          'body': 'bar',
          'userId': 1,
        },
        options: Options(
          headers: {
            'Content-type': 'application/json; charset=UTF-8',
          },
        ),
      );
      
      setState(() {
        _responseData = 'POST 응답:\n${response.data.toString()}';
      });
    } catch (e) {
      setState(() {
        _responseData = '오류 발생: ${e.toString()}';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dio HTTP 예제')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: _isLoading ? null : _fetchData,
                  child: Text('GET 요청'),
                ),
                ElevatedButton(
                  onPressed: _isLoading ? null : _postData,
                  child: Text('POST 요청'),
                ),
              ],
            ),
            SizedBox(height: 20),
            _isLoading
                ? Center(child: CircularProgressIndicator())
                : Expanded(
                    child: SingleChildScrollView(
                      child: Text(_responseData),
                    ),
                  ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dio 라이브러리는 더 많은 기능(인터셉터 &amp;amp; 취소 토큰 &amp;amp; 폼 데이터 &amp;amp; 파일 다운로드... 등)을 제공하는 강력한 HTTP 클라이언트입니다. API 응답으로 받은 JSON 데이터는 일반적으로 다음 과정을 통해 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. jsonDecode를 사용하여 JSON 문자열을 Dart 객체로 파싱&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 파싱된 데이터를 모델 클래스로 변환 (json_serializable &amp;amp; built_value 라이브러리 활용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효율적인 API 통신을 위해 캐싱 전략을 구현하거나, Retrofit과 같은 라이브러리를 사용하여 API 클라이언트를 정의할 수도 있습니다. 또한, 인터넷 연결 상태를 확인하기 위해서 connectivity 라이브러리를 함께 사용하는 것을 권장 드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;11. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Hot Reload와 Hot Restart의 차이점을 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Hot Reload는 상태를 유지하며 UI 변경 사항만 빠르게 적용하지만, Hot Restart는 앱을 완전히 재시작하여 모든 상태를 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hot Reload와 Hot Restart는 Flutter 개발의 생산성을 높이는 핵심 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Hot Reload&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741057176647&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Hot Reload 사용 방법
1. 단축키 사용
  ● Windows/Linux: Ctrl + S (파일 저장) 또는 Ctrl + \
  ● macOS: Cmd + S (파일 저장) 또는 Cmd + \

2. IDE 버튼 사용
  ● VS Code: 디버그 모드에서 상단 메뉴의 번개 모양 아이콘(⚡) 클릭
  ● Android Studio/IntelliJ: 상단 툴바의 번개 모양 아이콘(⚡) 클릭

3. Flutter CLI 사용 (터미널에서 앱 실행 중일 경우)
  ● 터미널에서 r 키를 누르면 Hot Reload가 실행됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯 트리를 재구성하지만, 앱의 상태(State)를 보존합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 다트 코드만 리컴파일하고 변경된 코드를 기존 다트 VM에 주입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 매우 빠르게 실행되며(일반적으로 1초 이내), UI 변경 사항을 즉시 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● initState와 같은 생명주기 메서드는 다시 실행되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Hot Restart&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741057202868&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Hot Restart 사용 방법
1. 단축키 사용
  ● Windows/Linux: Ctrl + Shift + \
  ● macOS: Cmd + Shift + \
2. IDE 버튼 사용
  ● VS Code: 디버그 모드에서 상단 메뉴의 재시작 아이콘( ) 클릭
  ● Android Studio/IntelliJ: 상단 툴바의 재시작 아이콘( ) 클릭
3. Flutter CLI 사용 (터미널에서 앱 실행 중일 경우)
  ● 터미널에서 R 키(대문자)를 누르면 Hot Restart가 실행됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 앱을 완전히 재시작하고 모든 상태를 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 전체 앱을 다시 빌드하고 새로운 다트 VM 인스턴스를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Hot Reload보다 시간이 더 걸립니다. (일반적으로 몇 초 정도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 상태 변경 &amp;amp; 생명주기 메서드 수정 &amp;amp; 전역 변수 변경... 등 Hot Reload로 적용되지 않는 변경 사항에 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 변경 사항만 있을 때는, Hot Reload가 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 로직 &amp;amp; 상태 관리 코드 &amp;amp; 생명주기 메서드 등을 변경했을 때는 Hot Restart가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;12. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter에서 Native 코드와의 통합 방법에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter에서는 플랫폼 채널을 통해 Dart 코드와 네이티브 코드(AOS/IOS) 간의 통신을 구현하며, 플러그인 형태로 패키징하여 재사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 네이티브 코드와의 통합은 주로 플랫폼 채널(Platfrom Channels)을 통해 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 메서드 채널(MethodChannel): 일회성 메서드 호출에 사용되며, Flutter에서 네이티브 기능을 호출하거나 결과를 받아올 때 주로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이벤트 채널(EventChannel): 지속적인 데이터 스트림을 처리할 때 사용하며, 센서 데이터나 연속적인 네이티브 이벤트를 Flutter로 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;메서드 채널 구현 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741057388715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Dart 코드
static const platform = MethodChannel('com.example.app/battery');

Future&amp;lt;int&amp;gt; getBatteryLevel() async {
  try {
    final int result = await platform.invokeMethod('getBatteryLevel');
    return result;
  } on PlatformException catch (e) {
    // 오류 처리
    return -1;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741057409694&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 네이티브 코드(Android Kotlin)

private val CHANNEL = &quot;com.example.app/battery&quot;

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
  super.configureFlutterEngine(flutterEngine)
  MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -&amp;gt;
    if (call.method == &quot;getBatteryLevel&quot;) {
      val batteryLevel = getBatteryLevel()
      result.success(batteryLevel)
    } else {
      result.notImplemented()
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;13. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter에서 라우팅과 네비게이션을 처리하는 방법에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter에서는 Navigator 위젯을 사용하여 화면 간 이동을 관리하며, 명명된 라우트와 동적 라우트 모두 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;기본 네비게이션&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741059831581&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 새 화면으로 이동
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) =&amp;gt; SecondScreen()),
);

// 이전 화면으로 돌아가기
Navigator.pop(context);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;명명된 라우트(Named Routes)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. MaterialApp에서 라우트를 정의&lt;/p&gt;
&lt;pre id=&quot;code_1741059875975&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) =&amp;gt; HomeScreen(),
    '/details': (context) =&amp;gt; DetailScreen(),
    '/settings': (context) =&amp;gt; SettingsScreen(),
  },
);

// 명명된 라우트로 이동
Navigator.pushNamed(context, '/details');&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 인수 전달&lt;/p&gt;
&lt;pre id=&quot;code_1741059887238&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 인수와 함께 라우트 이동
Navigator.pushNamed(
  context, 
  '/details',
  arguments: {'id': 123, 'title': '상품 상세'},
);

// 인수 받기
final args = ModalRoute.of(context)!.settings.arguments as Map;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. onGenerateRoute를 사용한 동적 라우트 생성&lt;/p&gt;
&lt;pre id=&quot;code_1741059904523&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == '/product') {
      final args = settings.arguments as Map;
      return MaterialPageRoute(
        builder: (context) =&amp;gt; ProductScreen(id: args['id']),
      );
    }
    return null;
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Go_Router 같은 라이브러리를 사용하면 더 복잡한 라우팅 시나리오 (딥 링크 &amp;amp; 중첩 라우팅... 등)를 쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;14. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter의 Form 위젯과 Form 검증 방법에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter의 Form 위젯은 사용자 입력을 수집하고 검증하기 위한 컨테이너로, GlobalKey와 validator 함수를 사용하여 입력 값을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Form 기본 구조&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060062286&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final _formKey = GlobalKey&amp;lt;FormState&amp;gt;();

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(
        // TextFormField 설정
      ),
      ElevatedButton(
        onPressed: () {
          if (_formKey.currentState!.validate()) {
            // 폼이 유효할 때 실행할 코드
            _formKey.currentState!.save();
          }
        },
        child: Text('제출'),
      ),
    ],
  ),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;TextFormField와 검증&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060095405&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextFormField(
  decoration: InputDecoration(labelText: '이메일'),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return '이메일을 입력해주세요';
    }
    if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
      return '유효한 이메일 형식이 아닙니다';
    }
    return null; // null 반환은 입력이 유효하다는 의미
  },
  onSaved: (value) {
    // 값 저장 로직
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Form 상태 관리&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060126357&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 폼 검증
if (_formKey.currentState!.validate()) {
  // 폼 데이터 저장
  _formKey.currentState!.save();
  // 추가 로직 실행
}

// 폼 초기화
_formKey.currentState!.reset();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;사용자 정의 FormField&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060141415&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FormField&amp;lt;bool&amp;gt;(
  initialValue: false,
  validator: (value) {
    if (value == false) {
      return '이용약관에 동의해야 합니다';
    }
    return null;
  },
  builder: (field) {
    return CheckboxListTile(
      title: Text('이용약관에 동의합니다'),
      value: field.value,
      onChanged: (newValue) {
        field.didChange(newValue);
      },
      subtitle: field.hasError 
          ? Text(field.errorText!, style: TextStyle(color: Colors.red))
          : null,
    );
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;AutovalidateMode 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060174958&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextFormField(
  autovalidateMode: AutovalidateMode.onUserInteraction,
  // 사용자가 입력할 때마다 검증 실행
)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;15. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Build Context의 context의 역할에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ BuildContext는 위젯의 위치를 위젯 트리 내에서 식별하고, 상위 위젯 &amp;amp; 테마 &amp;amp; 미디어 쿼리... 등의 정보에 접근할 수 있게 해주는 핵심 요소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BuildContext는 Flutter에서 매우 중요한 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;위젯 트리에서의 위치 정보&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060290269&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@override
Widget build(BuildContext context) {
  // context는 이 위젯의 위치를 나타냅니다
  return Container();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 각 위젯은 빌드될 때, 고유한 BuildContext를 받습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 이 context는 위젯 트리에서 해당 위젯의 위치를 나타냅니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● context를 통해 위젯은 자신의 상위 요소들에 접근할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;상위 위젯에 접근하기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060340375&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 가장 가까운 Scaffold 찾기
final scaffold = Scaffold.of(context);

// 가장 가까운 Form 찾기
final form = Form.of(context);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;InheritedWidget을 통한 데이터 접근&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060358361&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 테마 데이터 접근
final theme = Theme.of(context);
final primaryColor = theme.primaryColor;

// 미디어 쿼리 정보 접근
final screenWidth = MediaQuery.of(context).size.width;

// 현재 로케일 접근
final locale = Localizations.localeOf(context);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Navigator &amp;amp; 라우팅&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060380392&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 네비게이션에 사용
Navigator.push(context, MaterialPageRoute(builder: (context) =&amp;gt; NextScreen()));&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Provider와 같은 상태 관리 도구 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060396294&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Provider를 통한 데이터 접근
final myData = Provider.of&amp;lt;MyDataModel&amp;gt;(context);

// Watch vs Read
final watchingData = context.watch&amp;lt;MyModel&amp;gt;(); // 변경 감지
final readOnlyData = context.read&amp;lt;MyModel&amp;gt;(); // 한 번만 읽기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context는 Flutter의 모든 것의 중심에 있으며, 위젯 트리 내의 정보 &amp;amp; 테마 &amp;amp; 크기 &amp;amp; 의존성... 등 다양한 데이터에 접근하는 데에 필수적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;16. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter의 Key에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Key는 위젯을 고유하게 식별하고 위젯 트리가 재구축될 때, 상태를 보존하는 데에 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 Key는 위젯의 ID 역할을 하며, 특히 동적으로 변경되는 위젯 목록에서 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Key의 필요성&lt;/p&gt;
&lt;pre id=&quot;code_1741060546062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Key 없이 사용 (문제 발생 가능)
ListView(
  children: items.map((item) =&amp;gt; ListTile(title: Text(item.title))).toList(),
);

// Key를 사용하여 각 항목 식별
ListView(
  children: items.map((item) =&amp;gt; ListTile(
    key: ValueKey(item.id),
    title: Text(item.title),
  )).toList(),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● Flutter는 위젯 트리를 비교할 때, 동일한 타입과 위치에 있는 위젯은 같은 것으로 간주합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 동적 목록에서 항목을 추가 &amp;amp; 제거 &amp;amp; 재정렬할 때, Key가 없으면 위젯의 상태가 섞일 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 주요 Key 유형&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. ValueKey&lt;/p&gt;
&lt;pre id=&quot;code_1741060616247&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문자열, 숫자 등의 값으로 키 생성
ValueKey&amp;lt;String&amp;gt;('unique-id')&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. ObjectKey&lt;/p&gt;
&lt;pre id=&quot;code_1741060629862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체 자체를 키로 사용
ObjectKey(myObject)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. UniqueKey&lt;/p&gt;
&lt;pre id=&quot;code_1741060642011&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 매번 새로운 고유 키 생성
UniqueKey()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. GlobalKey&lt;/p&gt;
&lt;pre id=&quot;code_1741060653134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 위젯의 상태에 전역적으로 접근 가능
final GlobalKey&amp;lt;FormState&amp;gt; _formKey = GlobalKey&amp;lt;FormState&amp;gt;();

Form(
  key: _formKey,
  // ...
);

// 다른 곳에서 접근
_formKey.currentState!.validate();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* Key 사용 사례&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 리스트 항목 재정렬&lt;/p&gt;
&lt;pre id=&quot;code_1741060684702&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ListView(
  children: myList.map((item) =&amp;gt; ListTile(
    key: ValueKey(item.id), // id로 식별
    title: Text(item.title),
  )).toList(),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 위젯 상태 보존&lt;/p&gt;
&lt;pre id=&quot;code_1741060698513&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 키가 있는 StatefulWidget은 트리에서 위치가 변경되어도 상태 유지
MyStatefulWidget(key: ValueKey('my-widget'))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. GlobalKey로 다른 위젯의 상태 접근&lt;/p&gt;
&lt;pre id=&quot;code_1741060716293&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 스크롤 컨트롤러에 접근
final GlobalKey&amp;lt;ScrollableState&amp;gt; _scrollKey = GlobalKey();

// 다른 위젯에서 스크롤 시작
_scrollKey.currentState?.position.animateTo(...);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Key를 적절히 사용하면 Flutter 앱의 성능과 사용자 경험을 크게 향상시킬 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;17. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter의 Stream과 Stream 유형에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Stream은 비동기 데이터 시퀀스를 처리하는 방법으로, 시간에 따라 발생하는 이벤트나 데이터를 처리하는 데에 이상적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;span style=&quot;color: #006dd7;&quot;&gt; Stream 기본 개념&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060843059&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 스트림 구독
final subscription = myStream.listen(
  (data) =&amp;gt; print('데이터 수신: $data'),
  onError: (error) =&amp;gt; print('오류 발생: $error'),
  onDone: () =&amp;gt; print('스트림 완료'),
  cancelOnError: false,
);

// 구독 취소
subscription.cancel();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Stream은 시간이 지남에 따라, 비동기적으로 데이터가 흐르는 통로입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Future가 단일 값을 비동기적으로 반환하는 반면, Stream은 여러 값을 시간에 따라 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Stream 유형&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;1. SingleSubscriptionStream&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt; 기본 Stream 유형입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;● 한번에 하나의 리스너만 가질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt; 구독이 취소된 후 다시 구독할 수 없습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;2. BroadcastStream&lt;/p&gt;
&lt;pre id=&quot;code_1741060926959&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 브로드캐스트 스트림 생성
final broadcastStream = myStream.asBroadcastStream();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 여러 리스너를 동시에 가질 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 구독자가 없어도 계속 이벤트를 발생시킵니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;* StreamControlloe 사용&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741060980018&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final controller = StreamController&amp;lt;int&amp;gt;();

// 데이터 추가
controller.sink.add(1);
controller.sink.add(2);

// 오류 추가
controller.sink.addError('오류 발생');

// 스트림 구독
controller.stream.listen((data) =&amp;gt; print('받은 데이터: $data'));

// 완료 후 컨트롤러 닫기
controller.close();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Stream 변환 연산자&lt;/p&gt;
&lt;pre id=&quot;code_1741061014067&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 맵핑
stream.map((value) =&amp;gt; value * 2);

// 필터링
stream.where((value) =&amp;gt; value &amp;gt; 5);

// 시간 제한
stream.timeout(Duration(seconds: 5));

// 중복 제거
stream.distinct();

// 스트림 병합
Stream.merge([stream1, stream2]);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Stream은 실시간 데이터 &amp;amp; 사용자 입력 &amp;amp; 센서 데이터 &amp;amp; 네트워크 이벤트...등 지속적으로 변화하는 데이터를 처리하는 데에 매우 유용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;18. &lt;span style=&quot;color: #8a3db6;&quot;&gt;FutureBuilder와 StreamBuilder의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ FutureBuilder는 일회성 비동기 작업을 처리하지만, StreamBuilder는 연속적인 데이터 흐름을 처리하는 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FutureBuilder와 StreamBuilder는 모두 Flutter에서 비동기 데이터를 UI에 쉽게 표시할 수 있게 해주는 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;FutureBuilder&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741064103926&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FutureBuilder&amp;lt;String&amp;gt;(
  future: fetchData(), // 비동기 데이터 가져오기
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return Text('Data: ${snapshot.data}');
    }
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 단일 비동기 작업(Future)의 결과를 기다렸다가, 결과에 따라 UI를 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 한 번 완료되면 다시 실행되지 않는, 일회성 작업에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 네트워크 요청 &amp;amp; 파일 로딩과 같은 단일 결과를 반환하는 작업에 주로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;StreamBuilder&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741064334872&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StreamBuilder&amp;lt;int&amp;gt;(
  stream: countStream(), // 데이터 스트림 구독
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return Text('Count: ${snapshot.data}');
    }
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 지속적으로 데이터를 방출하는 Stream을 구독하고 새 데이터마다 UI를 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 실시간 데이터 &amp;amp; 연속적인 이벤트 &amp;amp; 지속적인 업데이트가 필요한 경우에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 실시간 채팅 &amp;amp; 센서 데이터 모니터링 &amp;amp; 주식 가격 업데이트... 등에 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;주요 차이점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● FutureBuilder는 한 번 데이터를 로드하고 완료되지만, StreamBuilder는 지속적으로 데이터 흐름을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● FutureBuilder의 future는 한 번만 실행되지만, StreamBuilder의 stream은 여러 값을 시간에 걸쳐 방출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● StreamBuilder는 사용이 끝나면 메모리 누수를 방지하기 위해 Stream을 닫아야 하지만, FutureBuilder는 이런 관리가 필요 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;19. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Flutter의 3가지 테스트(단위 테스트, 위젯 테스트, 통합 테스트)에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Flutter의 테스트 피라미드는 빠른 단위 테스트 &amp;amp; UI 중심의 위젯 테스트 &amp;amp; 전체 앱 기능을 검증하는 통합 테스트로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 앱의 품질을 보장하기 위해 세 가지 주요 테스트 방법을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;단위 테스트(Unit Test)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741064667580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 단위 테스트 예시
void main() {
  test('Counter value should be incremented', () {
    final counter = Counter();
    counter.increment();
    expect(counter.value, 1);
  });
  
  test('Counter value should be decremented', () {
    final counter = Counter();
    counter.decrement();
    expect(counter.value, -1);
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 개별 함수 &amp;amp; 메서드 &amp;amp; 클래스의 동작을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● UI와 독립적이며 가장 빠르게 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 비즈니스 로직 &amp;amp; 유틸리티 함수 &amp;amp; 모델 클래스... 등을 테스트하는 데에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;위젯 테스트 (Widget Test)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741064725647&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 위젯 테스트 예시
void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // 위젯 빌드
    await tester.pumpWidget(MyApp());

    // 초기 상태 확인
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // 버튼 탭 동작 시뮬레이션
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // 상태 변화 확인
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 개별 위젯이나 위젯 트리의 동작을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 실제 디바이스 없이 Flutter 엔진을 사용하여 위젯을 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯의 렌더링 &amp;amp; 상호 작용 &amp;amp; 상태 변화... 등을 테스트할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;통합 테스트(Intergration Test)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741064787478&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 통합 테스트 예시
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('Complete user flow test', (WidgetTester tester) async {
    // 앱 시작
    await tester.pumpWidget(MyApp());
    
    // 로그인 화면에서 사용자 정보 입력
    await tester.enterText(find.byKey(Key('email')), 'test@example.com');
    await tester.enterText(find.byKey(Key('password')), 'password123');
    await tester.tap(find.byKey(Key('login_button')));
    await tester.pumpAndSettle();
    
    // 홈 화면으로 이동했는지 확인
    expect(find.text('Welcome!'), findsOneWidget);
    
    // 상품 목록으로 이동
    await tester.tap(find.byKey(Key('products_button')));
    await tester.pumpAndSettle();
    
    // 상품 선택
    await tester.tap(find.text('Product 1'));
    await tester.pumpAndSettle();
    
    // 상세 화면에 정보가 표시되는지 확인
    expect(find.text('Product Details'), findsOneWidget);
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 전체 앱 또는 앱의 큰 부분의 동작을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 실제 디바이스나 에뮬레이터에서 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 여러 위젯 간의 상호 작용 &amp;amp; 네비게이션 &amp;amp; 백엔드 통신... 등을 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;각 테스트 유형의 특징&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 단위 테스트는 가장 빠르고 작성하기 쉽지만, UI와의 상호 작용은 테스트하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 위젯 테스트는 UI 컴포넌트를 효율적으로 테스트할 수 있지만, 전체 앱 흐름은 테스트하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 통합 테스트는 가장 현실적인 테스트를 제공하지만, 실행 속도가 느리고 설정이 복작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;20. &lt;span style=&quot;color: #8a3db6;&quot;&gt;WidgetsApp과 MaterialApp의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ MaterialApp은 WidgetsApp을 확장하여 Material Design 요소와 테마를 추가한 고수준 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WidgetsApp과 MaterialApp은 모두 Flutter 앱의 루트 위젯으로 사용될 수 있지만, 제공하는 기능과 디자인 철학에 차이가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;WidgetsApp&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741065000906&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WidgetsApp(
  color: Colors.blue,
  pageRouteBuilder: &amp;lt;T&amp;gt;(RouteSettings settings, WidgetBuilder builder) =&amp;gt;
      PageRouteBuilder&amp;lt;T&amp;gt;(
    settings: settings,
    pageBuilder: (BuildContext context, Animation&amp;lt;double&amp;gt; animation,
        Animation&amp;lt;double&amp;gt; secondaryAnimation) =&amp;gt; builder(context),
  ),
  home: MyCustomWidget(),
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Flutter의 가장 기본적은 앱 구조를 제공하는 저수준 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 특정 디자인 시스템에 종속되지 않는 기본 앱 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 라우팅 &amp;amp; 위젯 바인딩 &amp;amp; 지역화... 등 핵심 앱 기능만 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 디자인 요소나 테마는 포함하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 사용자 정의 디자인 시스템을 구축하거나 Material &amp;amp; Cupertino 디자인을 사용하지 않을 때, 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;MaterialApp&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741065092550&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
    visualDensity: VisualDensity.adaptivePlatformDensity,
  ),
  darkTheme: ThemeData.dark(),
  themeMode: ThemeMode.system,
  home: MyHomePage(),
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● WidgetsApp을 상속하여 Google의 Material Design 시스템을 통합한 고수준 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● Material 디자인 요소(AppBar &amp;amp; FloatingActionButton &amp;amp; Card... 등)와 테마를 자동으로 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 내장된 테마 지원 &amp;amp; 네비게이션 드로어 &amp;amp; 앱 바... 등 추가 기능을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 대부분의 Flutter 앱에서 사용되는 표준 앱 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;주요 차이점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 상속 관계: MaterialApp은 WidgetsApp을 확장(내부적으로 사용)합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 디자인 시스템: MaterialApp은 Material Design을 기본으로 제공하지만, WidgetsApp은 어떤 디자인 시스템도 제공하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 기본 위젯: MaterialApp은 Scaffold &amp;amp; AppBar... 등 Material 위젯을 사용하기 쉽게 해주는 반면, WidgetsApp은 이런 편의성을 제공하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 테마: MaterialApp은 포괄적인 테마 지원(밝은/어두운 테마 &amp;amp; 색상 시스템)을 제공하지만, WidgetsApp은 기본 테마 지원이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;21. &lt;span style=&quot;color: #8a3db6;&quot;&gt;Abstract (extends) &amp;amp; Interface (implements) &amp;amp; Mixin (with)에 대해 말해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;└ Dart에서 추상 클래스(extends)는 기본 구현을 제공하고, 인터페이스(implements)는 계약을 정의하며, 믹스인(with)은 여러 클래스에 코드를 재사용하는 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart는 객체지향 프로그래밍을 지원하며, 코드 구조화와 재사용을 위한 세 가지 주요 메커니즘을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;추상 클래스(Abstract Class) - extends&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741065400634&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 추상 클래스 예시
abstract class Vehicle {
  // 구현된 메서드
  void startEngine() {
    print('Engine started');
  }
  
  // 추상 메서드 (구현 없음)
  void accelerate();
  
  // 추상 getter
  int get wheels;
}

// 추상 클래스 상속
class Car extends Vehicle {
  @override
  void accelerate() {
    print('Car is accelerating');
  }
  
  @override
  int get wheels =&amp;gt; 4;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● abstract 키워드로 선언된 클래스로, 직접 인스턴스화할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 구현된 메서드와 추상 메서드(구현 없이 선언만 된 메서드)를 모두 포함할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 하위 클래스가 extends 키워드를 사용하여 상속받습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 하위 클래스는 추상 메서드를 반드시 구현해야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● Dart에서는 단일 상속만 지원하므로, 한 클래스는 하나의 클래스만 extends 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;인터페이스(Interface) - implements&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741065510217&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 인터페이스 역할을 하는 클래스
class DatabaseConnector {
  void connect() =&amp;gt; print('Connected to database');
  void disconnect() =&amp;gt; print('Disconnected from database');
  Future&amp;lt;List&amp;lt;Map&amp;gt;&amp;gt; query(String sql) async =&amp;gt; [];
}

// 인터페이스 구현
class MySQLConnector implements DatabaseConnector {
  @override
  void connect() {
    print('Connected to MySQL database');
  }
  
  @override
  void disconnect() {
    print('Disconnected from MySQL database');
  }
  
  @override
  Future&amp;lt;List&amp;lt;Map&amp;gt;&amp;gt; query(String sql) async {
    print('Executing query in MySQL: $sql');
    return [];
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● Dart에는 interface 키워드가 없지만, 모든 클래스는 암시적으로 인터페이스를 정의합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 클래스를 implements하면 해당 클래스의 모든 메서드와 속성을 구현해야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 구현하는 클래스는 메서드 시그니처만 사용하며, 원본 구현은 상속받지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 여러 인터페이스를 구현할 수 있어서 다중 상속의 일종을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;믹스인(Mixin) - with&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741065624279&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 믹스인 정의
mixin Logger {
  void log(String message) {
    print('LOG: $message');
  }
}

mixin Analytics {
  void trackEvent(String event) {
    print('EVENT TRACKED: $event');
  }
}

// 믹스인 사용
class UserService with Logger, Analytics {
  void createUser(String username) {
    log('Creating user: $username');
    trackEvent('user_created');
    // 사용자 생성 로직
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 여러 클래스에 코드를 재사용하기 위한 방법입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● mixin 키워드로 선언되며, 인스턴스화할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 클래스는 with 키워드를 사용하여 여러 믹스인을 포함할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 상속 계층 구조 없이 기능을 '혼합'하는 방법을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;● 다중 상속의 문제([1]다이아몬드 문제)를 피하면서 코드 재사용을 가능하게 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;세 가지 메커니즘의 비교&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 사용 시기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; ● 추상 클래스(extneds): 공통 기능을 가진 관련 클래스들의 기본 구현을 제공할 때, 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;● 인터페이스(implements): 서로 다른 클래스가 동일한 계약을 준수해야 할 때, 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;● 믹스인(with): 독립적인 기능을 여러 클래스에 추가하고자 할 때, 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 제한 사항&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;● extends: 단일 상속만 가능합니다. (한 클래스는 하나의 클래스만 상속)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;● implements: 여러 인터페이스를 구현할 수 있지만, 모든 메서드를 직접 구현해야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;● with: 여러 믹스인을 사용할 수 있으며, 충돌이 있을 경우 마지막으로 지정된 믹스인의 구현을 우선합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]다이아몬드 문제(Diamond Problem)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다이아몬드 문제는 다중 상속을 지원하는 프로그래밍 언어에서 발생할 수 있는 모호성 문제입니다. 이름이 &quot;다이아몬드&quot;인 이유는, 상속 계층 구조를 도식화했을 때, 형태가 다이아몬드 모양을 띠기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741065963569&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    A
   / \
  B   C
   \ /
    D&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다이아몬드 문제는 다음과 같은 상황에서 발생합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 기본 클래스 A가 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 클래스 B와 C가 모두 A를 상속받습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 클래스 D가 B와 C를 모두 상속받습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 발생 가능한 문제&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제는 클래스 A에 someMethod라는 메서드가 있고, B와 C클래스가 이 메서드를 다르게 오버라이드한 경우에 발생합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. D클래스가 someMethod를 호출할 때, B의 버전을 사용해야 할지, 아니면 C의 버전을 사용해야 할지 모호합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. A의 속성이 B와 C에서 중복으로 상속되어 D에서 두 번 존재해야 하는지, 아니면 한 번만 존재해야 하는지 모호합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ Dart에서의 다이아몬드 문제 해결&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dart는 다중 상속을 직접적으로 지원하지 않기 때문에 전통적인 다이아몬드 문제가 발생하지 않습니다. 대신, 믹스인을 사용할 때 비슷한 충돌이 발생할 수 있지만, Dart는 명확한 규칙을 가지고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741066212484&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mixin A {
  void method() {
    print('A.method');
  }
}

mixin B {
  void method() {
    print('B.method');
  }
}

class C with A, B {
  // B의 method()가 A의 method()를 오버라이드
}

void main() {
  C().method(); // 출력: B.method
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 예시에서 믹스인이 적용되는 순서에 따라 충돌이 해결됩니다. &quot;마지막으로 지정된 믹스인(B)&quot;의 구현이 우선됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;믹스인을 사용한 더 복잡한 예제&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741066323915&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Animal {
  void breathe() {
    print('Animal breathing');
  }
}

mixin Mammal on Animal {
  void feedMilk() {
    print('Feeding milk');
  }
  
  @override
  void breathe() {
    super.breathe();
    print('Mammal breathing');
  }
}

mixin Bird on Animal {
  void layEggs() {
    print('Laying eggs');
  }
  
  @override
  void breathe() {
    super.breathe();
    print('Bird breathing');
  }
}

// Platypus는 Animal을 상속받고, Mammal과 Bird 믹스인을 사용
class Platypus extends Animal with Mammal, Bird {
  // 여기서 breathe()는 어떻게 동작할까요?
}

void main() {
  Platypus().breathe();
  // 출력:
  // Animal breathing
  // Mammal breathing
  // Bird breathing
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 예제에서 breathe 메서드 호출은 상속과 믹스인 순서에 따라 처리됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. Animal.breathe 실행&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. Mammal.breathe에서 super.breathe가 Animal.breathe를 호출 후, 자신의 코드 실행&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. Bird.breathe에서 super.breathe가 Mammal.breathe를 호출 후, 자신의 코드 실행&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 Dart는 믹스인을 통해 다중 상속의 이점을 제공하면서도 전통적인 다이아몬드 문제를 피할 수 있는 방법을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 믹스인과 super 호출&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dart에서 믹스인의 super 호출은 믹스인 체인에서 이전 믹스인이나 상위 클래스를 참조합니다. 이를 통해 다이아몬드 문제가 발생할 수 있는 상황에서도 메서드 호출 순서가 명확해집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 설계는 다중 상속의 유연성을 제공하면서도 다이아몬드 문제로 인한 모호성을 피할 수 있게 해줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴&amp;nbsp;글을&amp;nbsp;끝까지&amp;nbsp;읽어주셔서&amp;nbsp;감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 면접 질문들을 정리하면서, 특히 FutureBuilder와 StreamBuilder의 차이점, 테스트 방법론, 그리고 Dart의 객체지향 프로그래밍 개념과 같은 중요한 주제들을 더 깊이 이해할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 다음 포스트에서 인사드리겠습니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <category>dart 프로그래밍</category>
      <category>flutter api 연동</category>
      <category>flutter 개발자</category>
      <category>flutter 면접</category>
      <category>flutter 비동기 처리</category>
      <category>flutter 상태관리</category>
      <category>flutter 성능 최적화</category>
      <category>flutter 위젯</category>
      <category>flutter 테스트</category>
      <category>riverpod 튜토리얼</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/573</guid>
      <comments>https://kwonputer.tistory.com/573#entry573comment</comments>
      <pubDate>Tue, 4 Mar 2025 09:49:30 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 면접 질문을 통해 알아보는 플러터 (2) - 개념</title>
      <link>https://kwonputer.tistory.com/572</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/573&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://kwonputer.tistory.com/573&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.https://kwonputer.tistory.com/571&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (1) - 목차안&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/573&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NYMxd/hyYm2kkaM0/1CJRQKtOG1OaZ1vs9Kjbxk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997,https://scrap.kakaocdn.net/dn/L114B/hyYjlMpevn/UbVIT8gXBVF9hNlLjAgOkk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997&quot; data-og-url=&quot;https://kwonputer.tistory.com/573&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/573&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/573&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NYMxd/hyYm2kkaM0/1CJRQKtOG1OaZ1vs9Kjbxk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997,https://scrap.kakaocdn.net/dn/L114B/hyYjlMpevn/UbVIT8gXBVF9hNlLjAgOkk/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.https://kwonputer.tistory.com/571&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (1) - 목차안&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;안녕하세요!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 포스트에서는 Flutter보다는 '개념'에 대한 면접 질문에 대해서 다루겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;실제 면접이라고 생각하고 포스트를 작성해 보겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;주관적인 생각을 바탕으로 작성한 포스트라서 참고만 부탁드립니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정확한 정보전달이 아닐 수도 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래의 목차를 참고해 주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1. 알고 있는 디자인 패턴에 대해서 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2. MVC, MVP, MVVM 패턴의 차이점을 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;3. 싱글톤 디자인 패턴의 장점과 단점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;4. 클린 아키텍처(Clean Architecture)를 반드시 사용해야 하는 이유를 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;5. 의존성 역전에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;6. 스레드와 프로세스, 멀티 스레드와 멀티 프로세스에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;7. 이미지 캐시(Memory, Disk)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;8. 해시(Hash)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;9. 대칭 키와 비대칭 키에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;10. 동기(Synchronous)와 비동기(Asynchronous)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;11. 접근 토큰(Access Token)과 갱신 토큰(Refresh Token)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;12. TCP와 UDP에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;13. Git의 Merge와 Rebase의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;14. GraphQL에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;15. CI/CD에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;16. 선언형 &amp;amp; 명령형 &amp;amp; 함수형 프로그래밍에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;17. 메모리 누수(Memory Leak)에 대한 설명과 방지하는 방법에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;18. BDD &amp;amp; TDD의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;19. SDK 개발과 서비스 개발의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;20. HTTP와 HTTPS의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;21. HTTPS의 SSL Handshaking에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;알고 있는 디자인 패턴에 대해서 말해주세요.&lt;/span&gt;&lt;br /&gt;└ 기억나는 건 디자인 패턴으로는 싱글톤 패턴과 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]빌더 패턴&lt;/span&gt;이 있고, 아키텍처 패턴으로는 MVC, MVP, MVVM, Clean Architecture가 있네요.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]빌더 패턴&lt;/span&gt;: 동일한 생성 과정으로 서로 다른 표현의 결과를 만들 수 있게 해주는 패턴입니다. 가독성이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 생성자 사용 (가독성 떨어짐)
User user = User(&quot;John&quot;, &quot;Doe&quot;, 30, &quot;john@example.com&quot;, &quot;123 Street&quot;, null, true);

// 빌더 패턴 사용 (가독성 좋음)
User user = UserBuilder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.firstName(&quot;John&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.lastName(&quot;Doe&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.age(30)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.email(&quot;john@example.com&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.address(&quot;123 Street&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.isActive(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;MVC, MVP, MVVM 패턴의 차이점을 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ MVC, MVP, MVVM, Clean Architecture는 모두 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]관심사 분리(SoC)&lt;/span&gt;를 위한 아키텍처 패턴입니다. MVC, MVP, MVVM의 차이점은 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]컴포넌트&lt;/span&gt; 간의 통신 방식이라고 생각합니다.&lt;br /&gt;MVC는 Model(데이터) &amp;amp; View(UI) &amp;amp; Controllor(비즈니스 로직)로 구성됩니다. 컨트롤러가 모델을 변경하고 모델이 변경되면 뷰를 업데이트하는 방식입니다. Flutter에서는 Widget을 View라고 볼 수 있고, State를 Model로 볼 수 있겠네요.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;MVP는 Model(데이터) &amp;amp; View(UI) &amp;amp; Presenter(비즈니스 로직)로 구성됩니다. View가 사용자에게 입력을 받으면 Presenter에 전달하고, Presenter가 Model과 통신한 후에 다시 View를 업데이트하는 구조입니다. MVP의 핵심은 View와 Model은 서로 모른다는 점입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;MVVM은 Model(데이터) &amp;amp; View(UI) &amp;amp; ViewModel(비즈니스 로직)로 구성됩니다. ViewModel이 상태를 감지하면 View에 알려서 View를 업데이트하고, View가 사용자에게 입력을 받으면 이를 ViewModel에 알려서 ViewModel이 Model과 통신해서 데이터를 변경하고 변경된 데이터를 View에게 알려서 View를 업데이트하는 구조입니다. MVVM의 핵심은 View와 Model이 서로 모른다는 점과 &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]데이터 바인딩&lt;/span&gt;입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;즉, MVC &amp;amp; MVP &amp;amp; MVVM은 모두 비슷합니다. 똑같은 목적을 가지고 있기 때문입니다. 처음에 말한 &lt;span style=&quot;color: #ee2323;&quot;&gt;관심사 분리(SoC)를 위해서 더 효율적인 방식을 찾다가 나온 아키텍처 패턴&lt;/span&gt;입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;주관적인 생각이지만, 디자인 패턴이란 어떠한 목적을 위해서 더 효율적인 코드를 생각하고 고민해서 적용하고 찾아내서 이름을 붙이는 거로 생각합니다. 그렇기에, 검색해서 나오는 디자인 패턴들은 과거의 영광이라고 부를 수도 있을 것 같습니다. 이를 무시하거나 깎아내리려는 의도가 아닙니다. 다만, 과거의 영광도 기억해야 하지만, 꾸준히 더 나은 방식을 찾아내기 위해 노력을 해야, 본인만의 새로운 디자인 패턴을 만들 수 있을거라는 생각을 합니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;(클린 아키텍처도 이미 한참 옛날에 나온 이론입니다. 이제는 IT 기기의 성능이 아주 좋아졌기 때문에 지금에 와서야 적용이 되는 거로 생각합니다. 사실, 과거의 영광만을 쫓아도 벅차고 충분합니다. 다만, 항상 마음가짐으로 향상심을 가지고 있어야 새로운 지식을 습득하는 데에 거부감이 적어지는 것 같습니다.)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1] 관심사 분리(SoC)&lt;/span&gt;: 각각의 레이어의 독립성을 확보하고 의존성을 줄이기 위함을 뜻합니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2] 컴포넌트&lt;/span&gt;: MVC로 예를 들면, Model &amp;amp; View &amp;amp; Controllor가 레이어가 되고, 그 안의 파일들이 컴포넌트라고 할 수 있습니다. 레이어가 더 큰 범위라고 생각하시면 됩니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3] 데이터바인딩&lt;/span&gt;: 데이터가 변경될 때, 자동으로 UI가 업데이트되는 메커니즘이라고 생각하시면 됩니다. Android의 Observer 패턴을 생각하시면 됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;싱글톤 디자인 패턴의 장점과 단점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 싱글톤 디자인 패턴의 장점으로는 단일 인스턴스를 보장하기 때문에 메모리 사용을 줄여줍니다. 그리고 인스턴스 생성 비용이 큰 경우에 효율적입니다. 또한, 전역 접근을 제공하기 때문에 데이터 공유가 쉽습니다.&lt;br /&gt;단점으로는 의존성 주입이 어렵기 때문에 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]단위 테스트&lt;/span&gt;가 어렵습니다. 멀티 스레드 환경에서 동기화 문제가 발생할 가능성이 높습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;멀티 스레드 환경에서 2개의 스레드(A, B)가 싱글톤 객체의 카운터 변수인 int counter = 0을 counter++를 통해 1을 증가시켰을 때, 원하는 결과값인 2이지만, 실제 결과값은 1이 나옵니다.&lt;br /&gt;아래는 &quot;counter++&quot;의 연산이 이루어지는 과정입니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;초기 counter 값: 0

1. 스레드 A: counter 값(0)을 읽음
2. 스레드 B: counter 값(0)을 읽음 (아직 A가 값을 업데이트하기 전)
3. 스레드 A: 0 + 1 = 1 계산
4. 스레드 B: 0 + 1 = 1 계산
5. 스레드 A: 값 1을 메모리에 씀
6. 스레드 B: 값 1을 메모리에 씀 (B는 A가 이미 쓴 값을 덮어씀)

최종 counter 값: 1 (원하는 값 2가 아님)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;단위 테스트를 하기 위해서는 모의 객체(Mock)가 필요한데, 싱글톤은 클래스 내부에서 직접 생성되고 접근하기 때문에 외부에서 다른 구현체로 교체하기가 어렵습니다. 즉, 모의 객체(Mock)를 사용하기가 어렵습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1] 단위 테스트&lt;/span&gt;: 코드의 가장 작은 단위(&lt;span style=&quot;color: #006dd7;&quot;&gt;[2]함수 &amp;amp; 메서드 &amp;amp; 클래스&lt;/span&gt;)를 격리해서 해당 단위만 정확하게 작동하는지 검증하는 테스트를 말합니다. 단위 테스트에는 핵심 원칙이 있습니다. 다른 외부 요소(네트워크, 데이터베이스, 파일 시스템 등)에 의존하지 않고 독립적으로 실행되어야 합니다. 그렇기에 실제 데이터가 아닌 모의 객체(Mock)를 사용합니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2] 함수 &amp;amp; 메서드&lt;/span&gt;: 함수는 독립적으로 사용할 수 있는 코드를 말하고, 메서드는 클래스 내부에서 변수를 활용해 구현된 코드라고 보시면 됩니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;클린 아키텍처(Clean Architecture)를 반드시 사용해야 하는 이유를 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ MVC, MVP, MVVM과의 차이점이 있다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;외부 프레임워크나 라이브러리 변경에도 핵심 비즈니스 로직을 보호&lt;/span&gt;할 수 있습니다. 또한, 프로젝트의 테스트 용이성 &amp;amp; 유지 보수성 &amp;amp; 확장성이 높아집니다.&amp;nbsp;&lt;br /&gt;● 의존성 역전을 통해 의존성의 방향이 항상 외부에서 내부로 향하기 때문에 핵심 비즈니스 로직이 외부 변화에 영향을 받지 않습니다.&lt;br /&gt;● 관심사 분리(SoC)로 코드 이해와 유지보수가 쉬워집니다. (어느 프로젝트를 열어도 같은 구조와 패턴이기 때문입니다)&lt;br /&gt;● 각 계층(data, domain, presenter)별로 독립적인 테스트가 가능합니다.&lt;br /&gt;● &amp;nbsp;프레임워크 &amp;amp; 라이브러리의 교체가 용이합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;5. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;의존성 역전에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 의존성 역전의 핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;서로 의존해서는 안 되며, 추상화에 의존해야 한다&lt;/span&gt;는 점입니다. 예를 들어, 비즈니스 로직 클래스가 데이터베이스 클래스에 직접 의존하게 되면, &lt;span style=&quot;color: #333333;&quot;&gt;데이터베이스 변경 시, 비즈니스 로직도 수정&lt;/span&gt;해야 합니다. 하지만, 인터페이스(추상화)를 통해 의존성을 역전시키면, 비즈니스 로직은 변경 없이 다양한 데이터 베이스 구현체를 사용할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;6. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;스레드와 프로세스, 멀티 스레드와 멀티 프로세스에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 프로세스는 독립적인 실행 단위이고, 스레드는 프로세스 내부에서 실행되는 더 작은 실행 단위입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[1]프로세스&lt;/span&gt;는 독립적인 실행 단위이며, 자체 메모리 공간과 시스템 자원을 가집니다. 그리고 각 프로세스는 서로 격리되어 있어 한 프로세스의 문제가 다른 프로세스에 영향을 주지 않습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[2]스레드&lt;/span&gt;는 프로세스 내부에서 실행되는 더 작은 실행 단위이며, 같은 프로세스 내의 스레드들은 서로 메모리와 자원을 공유합니다. 이에 따라 스레드 간 통신이 더 효율적이지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;한 스레드의 오류가 전체 프로세스에 영향을 줄 수 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[4]멀티 프로세스&lt;/span&gt;는 여러 프로세스를 동시에 실행하는 방식이며, 높은 안정성을 제공합니다. 하지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;프로세스 간 통신 비용이 큽니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[3]멀티 스레드&lt;/span&gt;는 하나의 프로세스 내에서 여러 스레드를 동시에 실행하는 방식이며, &lt;span style=&quot;color: #ee2323;&quot;&gt;자원 공유가 효율적이지만 동기화 문제가 발생할 가능성&lt;/span&gt;이 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Flutter로 추가 설명을 해보겠습니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]프로세스&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 앱 실행 코드
void main() {
&amp;nbsp;&amp;nbsp;runApp(MyApp());&amp;nbsp;&amp;nbsp;// 이 코드가 실행되면 앱 프로세스가 시작됩니다
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Flutter 앱 자체가 하나의 프로세스라고 볼 수 있습니다. AOS &amp;amp; IOS에서 Flutter로 만든 앱이 실행될 때, 운영체제(OS)는 해당 앱에 대한 별도의 프로세스를 할당합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]스레드&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// UI 업데이트 - 메인 스레드에서 실행
setState(() {
&amp;nbsp;&amp;nbsp;counter++;&amp;nbsp;&amp;nbsp;// 이 코드는 메인 스레드에서 실행됩니다
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Flutter에서 기본적으로 모든 코드는 메인 스레드(UI 스레드)에서 실행됩니다.&amp;nbsp;&lt;br /&gt;● 모든 UI 업데이트&lt;br /&gt;● 애니메이션&lt;br /&gt;● 사용자 입력 처리&lt;br /&gt;● ...등등&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]멀티 스레드&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'package:flutter/foundation.dart';

// 복잡한 연산을 수행하는 함수
int calculateFactorial(int n) {
&amp;nbsp;&amp;nbsp;int result = 1;
&amp;nbsp;&amp;nbsp;for (int i = 2; i &amp;lt;= n; i++) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result *= i;
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;return result;
}

// 앱에서 사용
void onButtonPressed() async {
&amp;nbsp;&amp;nbsp;// 별도의 Isolate에서 계산 실행
&amp;nbsp;&amp;nbsp;final result = await compute(calculateFactorial, 20);
&amp;nbsp;&amp;nbsp;setState(() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factorialResult = result;
&amp;nbsp;&amp;nbsp;});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Flutter에서는 전통적인 스레드 대신에 Isolate를 사용합니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;Isolate는 자체 메모리를 가진 별도의 실행 환경으로 다른 Isolate와 메모리를 공유하지 않는다&lt;/span&gt;는 점이 특징입니다.&lt;br /&gt;또한, Flutter는 복잡한 Isolate 설정을 간소화하기 위해 compute 함수를 제공합니다.&lt;br /&gt;● 대용량 JSON 파싱&lt;br /&gt;● 이미지 처리/필터링&lt;br /&gt;● 복잡한 연산(암호화, 해시 계산... 등)&lt;br /&gt;● 대용량 파일처리&lt;br /&gt;● ...등등&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]멀티 프로세스&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Flutter 측 코드
const platform = MethodChannel('com.example.app/background_service');

Future&amp;lt;void&amp;gt; startBackgroundService() async {
&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await platform.invokeMethod('startService');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print('백그라운드 서비스 시작됨');
&amp;nbsp;&amp;nbsp;} catch (e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print('에러: $e');
&amp;nbsp;&amp;nbsp;}
}

// Android에서 별도의 서비스(프로세스) 실행 (Kotlin 코드)
// Android 측 코드 (MainActivity.kt)
/*
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
&amp;nbsp;&amp;nbsp;super.configureFlutterEngine(flutterEngine)
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;MethodChannel(flutterEngine.dartExecutor.binaryMessenger, &quot;com.example.app/background_service&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.setMethodCallHandler { call, result -&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (call.method == &quot;startService&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val intent = Intent(this, BackgroundService::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startService(intent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.success(null)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.notImplemented()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Flutter는 멀티 프로세스를 지원하지 않지만, 플랫폼 채널(Platform Channels)을 통해 다른 프로세스와 통신을 할 수 있습니다.&lt;br /&gt;● 위치 추적 백그라운드 서비스&lt;br /&gt;● 음악 재생 서비스&lt;br /&gt;● 푸시 알림 처리&lt;br /&gt;● 지속적인 네트워크 모니터링&lt;br /&gt;● ...등등&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;7. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;이미지 캐시(Memory, Disk)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 이미지 캐시는 앱의 성능을 향상시키기 위해 이미지를 메모리나 디스크에 저장하는 최적화 기술입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;메모리 캐시는 RAM에 이미지를 저장하는 방식으로, 접근 속도가 매우 빠르지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;용량이 제한적이고 앱이 종료되면 데이터가 사라집니다.&lt;/span&gt;&lt;br /&gt;주로 현재 화면에 표시되는 이미지나 자주 사용되는 이미지를 저장할 때 사용합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;디스크 캐시는 기기의 저장소에 이미지를 저장하는 방식으로, 메모리보다 용량이 크고 앱이 재시작되어도 데이터가 유지됩니다. 다만 &lt;span style=&quot;color: #ee2323;&quot;&gt;접근 속도가 메모리보다 느립니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;8. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;해시(Hash)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 해시는 임의의 크기를 가진 데이터를 고정된 크기의 값으로 변환하는 함수입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;해시 함수의 주요 특성은 일방향성입니다. 해시값에서 원본 데이터를 복원하는 것은 불가능에 가깝습니다. 또한, 입력값이 조금만 달라져도 출력값이 크게 달라지는 특성(눈사태 효과)을 가집니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;먼저, 해시를 좀 더 쉽게 설명해 보겠습니다.&lt;br /&gt;해시는 어떤 데이터든지 특정 '지문'처럼 고유한 값으로 변환하는 과정을 말합니다.&lt;br /&gt;1. 지문 생성기: 여러분이 어떤 사람(데이터)이든 그 사람의 지문(해시값)을 추출하는 기계를 가지고 있습니다.&lt;br /&gt;2. 크기 통일: 키가 190cm인 사람이든 키가 150cm인 사람이든 상관없이, 지문은 항상 일정한 크기로 나옵니다.&lt;br /&gt;3. 고유성: 다른 사람이면 다른 지문이 나옵니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음으로 &quot;임의의 크기를 가진 데이터를 고정된 크기의 값으로 변환&quot;을 좀 더 쉽게 설명해 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&quot;안녕하세요&quot; &amp;rarr; a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
&quot;긴 소설 전체...&quot; &amp;rarr; 다른 64글자 해시값
&quot;대용량 동영상...&quot; &amp;rarr; 또 다른 64글자 해시값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;해시 함수는 모든 다양한 크기의 데이터를 입력받아서, 항상 동일한 길이(ex. 256비트 &amp;amp; 64글자)의 문자열이나 숫자로 변환합니다. 이것이 바로 &quot;임의의 크기 -&amp;gt; 고정된 크기&quot;를 뜻합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;해시에 대한 Dart 예시를 보여드릴게요.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'dart:convert';
import 'package:crypto/crypto.dart';

void main() {
&amp;nbsp;&amp;nbsp;// 다양한 길이의 입력 문자열
&amp;nbsp;&amp;nbsp;String shortText = &quot;안녕하세요&quot;;
&amp;nbsp;&amp;nbsp;String mediumText = &quot;이것은 중간 길이의 텍스트입니다. 개발하는 것은 재미있습니다.&quot;;
&amp;nbsp;&amp;nbsp;String longText = &quot;아주 긴 텍스트...&quot; * 1000; // 매우 긴 텍스트
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// SHA-256 해시 계산 (항상 64글자 16진수 문자열 생성)
&amp;nbsp;&amp;nbsp;String shortHash = sha256.convert(utf8.encode(shortText)).toString();
&amp;nbsp;&amp;nbsp;String mediumHash = sha256.convert(utf8.encode(mediumText)).toString();
&amp;nbsp;&amp;nbsp;String longHash = sha256.convert(utf8.encode(longText)).toString();
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;print(&quot;짧은 텍스트 해시 (길이: ${shortHash.length}): $shortHash&quot;);
&amp;nbsp;&amp;nbsp;print(&quot;중간 텍스트 해시 (길이: ${mediumHash.length}): $mediumHash&quot;);
&amp;nbsp;&amp;nbsp;print(&quot;긴 텍스트 해시 (길이: ${longHash.length}): $longHash&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;짧은 텍스트 해시 (길이: 64): a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
중간 텍스트 해시 (길이: 64): 5d41402abc4b2a76b9719d911017c592
긴 텍스트 해시 (길이: 64): 7b502c3a1f48c8609ae212cdfb639dee&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;모든 결과는 길이가 동일하며, 입력 데이터가 다르다면 출력 해시값도 완전히 다릅니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;해시의 대표적인 실제 활용 사례로 5개를 알려드리겠습니다.&lt;br /&gt;1. 비밀번호 저장&lt;br /&gt;● 실제 비밀번호를 저장하지 않고 해시값만 저장&lt;br /&gt;● 사용자가 로그인할 때, 입력한 비밀번호의 해시값과 저장된 해시값을 비교&lt;br /&gt;&amp;nbsp;&lt;br /&gt;2. 데이터 무결성 검증&lt;br /&gt;● 파일 다운로드 후, 해시값을 계산하여 원본과 비교&lt;br /&gt;● 데이터가 변조되었는지 확인 가능&lt;br /&gt;&amp;nbsp;&lt;br /&gt;3. Flutter에서 위젯 키 관리&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class MyWidget extends StatelessWidget {
&amp;nbsp;&amp;nbsp;final String id;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;MyWidget({required this.id}) : super(key: ValueKey(id));
&amp;nbsp;&amp;nbsp;// id 문자열이 해시 함수를 통해 고유한 키 값으로 변환
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;Widget build(BuildContext context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Container(/* ... */);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;4. 캐싱 시스템&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;String getCacheFileName(String url) {
&amp;nbsp;&amp;nbsp;return sha256.convert(utf8.encode(url)).toString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● URL이나 키를 해시하여 캐시 파일 이름으로 사용&lt;br /&gt;&amp;nbsp;&lt;br /&gt;5. 해시 테이블(HashMap)&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;final Map&amp;lt;String, User&amp;gt; userCache = {};
// 여기서 String 키는 내부적으로 해시값을 사용하여 데이터를 효율적으로 찾음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 빠른 검색을 위한 자료 구조&lt;br /&gt;&amp;nbsp;&lt;br /&gt;위에 적힌 5개의 실제 사례를 보시면 해시의 특성이 이해하기 쉬워집니다.&lt;br /&gt;* 해시의 특성&lt;br /&gt;● 일방향성: 해시값에서 원래 데이터를 복원할 수 없음&lt;br /&gt;● 결정성: 같은 입력은 항상 같은 해시값 생성&lt;br /&gt;● 눈사태 효과: 입력이 조금만 달라도 출력은 완전히 달라짐&lt;br /&gt;● 고속 계산: 큰 데이터라도 빠르게 해시값 계산 가능&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그리고 해시에는 단점이 있습니다. 그건 바로 '해시 충돌'입니다. 서로 다른 두 입력이 동일한 해시값을 생성하는 경우를 '충돌'이라고 합니다. 해시 공간은 유한하지만, 가능한 입력은 무한하기 때문에 충돌은 이론적으로 피할 수 없습니다.&lt;br /&gt;Dart의 Map이나 Set에서는 이러한 충돌을 내부적으로 처리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class Person {
&amp;nbsp;&amp;nbsp;final String name;
&amp;nbsp;&amp;nbsp;final int age;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;Person(this.name, this.age);
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 해시 코드 재정의
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;int get hashCode =&amp;gt; name.hashCode ^ age.hashCode;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 동등성 비교 재정의
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;bool operator ==(Object other) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (identical(this, other)) return true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return other is Person &amp;amp;&amp;amp; other.name == name &amp;amp;&amp;amp; other.age == age;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;hashCode는 두 속성의 해시값을 XOR 연산하여 생성합니다. 충돌이 발생해도 '=='연산자를 통해 추가 확인을 수행합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;9. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;대칭 키와 비대칭 키에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 대칭 키는 하나의 키로 암호화 &amp;amp; 복호화를 모두 수행하고, 비대칭 키는 공개 키와 개인 키를 쌍으로 사용하여 더 높은 보안을 제공합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;대칭 키 암호화는&lt;span style=&quot;color: #009a87;&quot;&gt; 동일한 키&lt;/span&gt;를 사용하여 데이터를 암호화하고 복호화하는 방식입니다. 처리 속도가 빠르고 구현이 간단하지만, 통신 당사자 간에 안전하게 키를 공유하는 것이 중요합니다. 대표적으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]DES&lt;/span&gt;, &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]AES&lt;/span&gt;가 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;비대칭 키 암호화는 &lt;span style=&quot;color: #009a87;&quot;&gt;공개 키와 개인 키&lt;/span&gt;라는 두 개의 서로 다른 키를 사용합니다. 공개 키로 암호화한 데이터는 개인 키로만 복호화할 수 있고, 개인 키로 암호화한 데이터는 공개 키로만 복호화할 수 있습니다. 대표적으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]RSA&lt;/span&gt;, &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]ECC&lt;/span&gt;가 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;실제 애플리케이션에서는 대칭 키와 비대칭 키의 장점을 결합한 하이브리드 접근 방식도 사용합니다. 비대칭 키로 세션 키(대칭 키)를 안전하게 교환한 후, 실제 데이터 암호화에는 더 빠른 대칭 키를 사용하는 방식입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]DES&lt;/span&gt;: 아주 예전에 개발된 초기 표준 암호화 알고리즘입니다. 56비트 키를 사용하여 64비트 블록 단위로 데이터를 암호화합니다. 현재는 키 길이가 짧아서 안전하지 않다고 간주하여 거의 사용하지 않습니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]AES&lt;/span&gt;: 현재 가장 널리 사용되는 대칭 키 알고리즘입니다. 128 &amp;amp; 192 &amp;amp; 256비트 키 길이를 사용합니다. 데이터를 블록 단위로 처리하며 매우 효율적으로 동작합니다. 모바일 앱에서는 로컬 데이터 암호화에 주로 사용합니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]RSA&lt;/span&gt;: 현재 가장 널리 사용되는 비대칭 키 알고리즘입니다. 1024 &amp;amp; 2048 &amp;amp; 4096비트 키 길이를 사용합니다. 디지털 서명과 키 교환에 주로 사용합니다. 다만, 연산이 복잡하여 대칭 키보다 속도가 느립니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]ECC&lt;/span&gt;: RSA보다 짧은 키 길이로도 동등한 보안 수준을 제공(256비트 ECC = 3072비트 RSA)합니다. 모바일 환경에 적합한 경량 암호화 방식입니다. RSA보다 빠른 속도와 낮은 자원을 사용합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;ECC보다 RSA를 많이 사용하는 이유&lt;/span&gt;: RSA는 오래전에 개발되어 광범위하게 분석되고 검증되었습니다. 보안 시스템에서 신뢰도와 검증 기간은 매우 중요한 요소입니다. RSA는 ECC보다 상대적으로 구현이 쉽습니다. 또한, 대부분의 개발자가이 이미 RSA에 익숙합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Flutter에서 대칭 키 알고리즘 AES 사용 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'package:encrypt/encrypt.dart';
import 'package:encrypt/encrypt_io.dart';

void main() {
&amp;nbsp;&amp;nbsp;// 키 생성 (256비트 = 32바이트)
&amp;nbsp;&amp;nbsp;final key = Key.fromLength(32);
&amp;nbsp;&amp;nbsp;final iv = IV.fromLength(16);&amp;nbsp;&amp;nbsp;// 초기화 벡터
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 암호화할 평문
&amp;nbsp;&amp;nbsp;final plainText = 'Flutter에서 AES 암호화 예제입니다';
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 암호화
&amp;nbsp;&amp;nbsp;final encrypter = Encrypter(AES(key));
&amp;nbsp;&amp;nbsp;final encrypted = encrypter.encrypt(plainText, iv: iv);
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;print('암호화된 텍스트: ${encrypted.base64}');
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 복호화
&amp;nbsp;&amp;nbsp;final decrypted = encrypter.decrypt(encrypted, iv: iv);
&amp;nbsp;&amp;nbsp;print('복호화된 텍스트: $decrypted');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Flutter에서 비대칭 키 알고리즘 RSA 사용 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'package:fast_rsa/fast_rsa.dart';

Future&amp;lt;void&amp;gt; main() async {
&amp;nbsp;&amp;nbsp;// 키 쌍 생성
&amp;nbsp;&amp;nbsp;final keyPair = await RSA.generate(2048);
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 암호화할 평문
&amp;nbsp;&amp;nbsp;final plainText = '비대칭 키 암호화 예제입니다';
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 공개 키로 암호화
&amp;nbsp;&amp;nbsp;final encrypted = await RSA.encryptPKCS1v15(plainText, keyPair.publicKey);
&amp;nbsp;&amp;nbsp;print('암호화된 텍스트: $encrypted');
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 개인 키로 복호화
&amp;nbsp;&amp;nbsp;final decrypted = await RSA.decryptPKCS1v15(encrypted, keyPair.privateKey);
&amp;nbsp;&amp;nbsp;print('복호화된 텍스트: $decrypted');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Flutter에서 하이브리드 암호화 구현 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'dart:convert';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/key_generators/rsa_key_generator.dart';
import 'package:pointycastle/random/fortuna_random.dart';

Future&amp;lt;void&amp;gt; hybridEncryptionExample() async {
&amp;nbsp;&amp;nbsp;// 1. RSA 키 쌍 생성 (실제로는 서버의 공개 키를 사용)
&amp;nbsp;&amp;nbsp;final keyPair = await generateRSAKeyPair();
&amp;nbsp;&amp;nbsp;final publicKey = keyPair.publicKey as RSAPublicKey;
&amp;nbsp;&amp;nbsp;final privateKey = keyPair.privateKey as RSAPrivateKey;

&amp;nbsp;&amp;nbsp;// 2. 임시 AES 세션 키 생성
&amp;nbsp;&amp;nbsp;final sessionKey = Key.fromSecureRandom(32); // 256비트 AES 키
&amp;nbsp;&amp;nbsp;final iv = IV.fromSecureRandom(16);

&amp;nbsp;&amp;nbsp;// 3. 데이터 준비
&amp;nbsp;&amp;nbsp;final message = 'Flutter 앱에서 중요한 데이터를 안전하게 전송하는 예제';

&amp;nbsp;&amp;nbsp;// 4. 세션 키로 데이터 암호화 (AES - 대칭 암호화)
&amp;nbsp;&amp;nbsp;final aesEncrypter = Encrypter(AES(sessionKey));
&amp;nbsp;&amp;nbsp;final encryptedData = aesEncrypter.encrypt(message, iv: iv);

&amp;nbsp;&amp;nbsp;// 5. 공개 키로 세션 키 암호화 (RSA - 비대칭 암호화)
&amp;nbsp;&amp;nbsp;final rsaEncrypter = Encrypter(RSA(publicKey: publicKey));
&amp;nbsp;&amp;nbsp;final encryptedSessionKey = rsaEncrypter.encrypt(base64.encode(sessionKey.bytes));

&amp;nbsp;&amp;nbsp;print('암호화된 데이터: ${encryptedData.base64}');
&amp;nbsp;&amp;nbsp;print('암호화된 세션 키: ${encryptedSessionKey.base64}');

&amp;nbsp;&amp;nbsp;// --- 수신자 측 (서버 또는 다른 앱) ---

&amp;nbsp;&amp;nbsp;// 6. 개인 키로 세션 키 복호화
&amp;nbsp;&amp;nbsp;final rsaDecrypter = Encrypter(RSA(privateKey: privateKey));
&amp;nbsp;&amp;nbsp;final decryptedSessionKeyStr = rsaDecrypter.decrypt(encryptedSessionKey);
&amp;nbsp;&amp;nbsp;final decryptedSessionKey = Key(base64.decode(decryptedSessionKeyStr));

&amp;nbsp;&amp;nbsp;// 7. 복호화된 세션 키로 데이터 복호화
&amp;nbsp;&amp;nbsp;final aesDecrypter = Encrypter(AES(decryptedSessionKey));
&amp;nbsp;&amp;nbsp;final decryptedData = aesDecrypter.decrypt(encryptedData, iv: iv);

&amp;nbsp;&amp;nbsp;print('복호화된 데이터: $decryptedData');
}

// RSA 키 쌍 생성 함수
Future&amp;lt;AsymmetricKeyPair&amp;lt;RSAPublicKey, RSAPrivateKey&amp;gt;&amp;gt; generateRSAKeyPair() async {
&amp;nbsp;&amp;nbsp;// RSA 키 생성 로직...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;하이브리드 암호화 방식은 크게 5가지의 단계로 진행됩니다.&lt;br /&gt;1. 임시 대칭 키 생성: 통신할 때마다 새로운 임시 대칭 키(세션 키)를 생성합니다. 이 키는 AES와 같은 대칭 암호화에 사용됩니다.&lt;br /&gt;2. 세션 키 암호화: 수신자의 공개 키를 사용하여 세션 키를 암호화합니다. 이렇게 암호화된 세션 키는 수신자의 개인 키로만 복호화할 수 있습니다.&lt;br /&gt;3. 데이터 암호화: 실제 데이터는 생성된 세션 키를 사용하여 대칭 암호화로 처리합니다.&lt;br /&gt;4. 데이터 전송: 암호화된 세션 키와 암호화된 데이터를 모두 전송합니다.&lt;br /&gt;5. 수신자 측 복호화: 수신자는 자신의 개인 키로 세션키를 복호화합니다. 그리고 복호화된 세션 키를 사용하여 실제 데이터를 복호화합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;대표적으로 하이브리드 암호화를 사용한 실제 사례는 HTTPS 통신입니다.&lt;br /&gt;핸드셰이크 단계와 데이터 교환 단계를 통해 하이브리드 암호화를 수행합니다.&lt;br /&gt;면접 질문에 HTTPS SSL Handshaking이 있어서 자세한 건 아래에서 설명하겠습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;10. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;동기(Synchronous)와 비동기(Asynchronous)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 동기(Synchronous) 코드는 순차적으로 실행되며 작업이 완료될 때까지 다음 코드가 실행되지 않고, 비동기(Asynchronous) 코드는 작업 완료를 기다리지 않고 다음 코드를 실행할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;동기(Synchronous) 처리는 코드가 순차적으로 실행되며, 하나의 작업이 완료된 후에 다음 작업이 시작됩니다. 이는 코드 흐름을 예측하기 쉽지만, 시간이 오래 걸리는 작업(네트워크 요청, 파일 입출력... 등)이 있으면 &lt;span style=&quot;color: #ee2323;&quot;&gt;전체 프로그램이 차단(blocking)&lt;/span&gt;될 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;비동기(Asynchronous) 처리는 작업 완료를 기다리지 않고 다음 코드를 실행할 수 있게 해주며, 작업이 완료되면 콜백이나 기타 메커니즘을 통해 결과를 처리합니다. 이는 UI 응답성을 유지하고 자원을 효율적으로 사용할 수 있게 해줍니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Flutter에서는 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]Future&lt;/span&gt;와 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]async &amp;amp; await&lt;/span&gt;을 통해 비동기 프로그래밍을 지원합니다.&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]Future&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Future 객체의 기본 형태
Future&amp;lt;String&amp;gt; fetchUserName() {
&amp;nbsp;&amp;nbsp;// 서버에서 사용자 이름을 가져오는 비동기 작업
&amp;nbsp;&amp;nbsp;return Future.delayed(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Duration(seconds: 2),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;() =&amp;gt; &quot;John Doe&quot;&amp;nbsp;&amp;nbsp;// 2초 후에 이 값을 반환
&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Future는 비동기 작업의 결과를 나타내는 객체입니다. 이것은 &quot;미래의 어느 시점에 완료될 값 또는 에러&quot;를 나타냅니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;void handleFutureWithCallbacks() {
&amp;nbsp;&amp;nbsp;fetchUserName()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.then((userName) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 성공적으로 값을 받았을 때 실행
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;사용자 이름: $userName&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.catchError((error) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 에러가 발생했을 때 실행
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;에러 발생: $error&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.whenComplete(() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 성공이든 실패든 완료되었을 때 실행
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;작업 완료&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Future를 처리하는 방법은 크게 두 가지가 있습니다. 첫 번째는 then &amp;amp; catchError &amp;amp; whenComplete 메서드를 사용하는 방법입니다. 이 방식은 콜백 기반 접근법으로, &lt;span style=&quot;color: #ee2323;&quot;&gt;여러 비동기 작업을 연결할 때, 코드가 복잡&lt;/span&gt;해질 수 있습니다. (콜백 지옥)&lt;br /&gt;두 번째는 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]async &amp;amp; await&lt;/span&gt; 방법입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]async &amp;amp; await&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// async 키워드로 함수를 비동기 함수로 표시
Future&amp;lt;void&amp;gt; handleFutureWithAsyncAwait() async {
&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// await 키워드로 Future의 완료를 기다림
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String userName = await fetchUserName();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;사용자 이름: $userName&quot;);
&amp;nbsp;&amp;nbsp;} catch (error) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;에러 발생: $error&quot;);
&amp;nbsp;&amp;nbsp;} finally {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;작업 완료&quot;);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;async &amp;amp; await 방식을 사용하면 비동기 코드를 마치 동기 코드처럼 직관적으로 작성할 수 있습니다. 코드 흐름을 따라가기 쉽고 오류 처리도 try-catch 구문으로 간단하게 처리할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;11. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;접근 토큰(Access Token)과 갱신 토큰(Refresh Token)에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 접근 토큰(Access Token)은 보호된 리소스에 접근 권한을 부여하는 단기 자격 증명이며, 갱신 토큰(Refresh Token)은 접근 토큰이 만료되었을 때 새로운 접근 토큰을 발급받기 위한 장기 자격 증명입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;접근 토큰(Access Token)은 클라이언트가 서버의 보호된 리소스에 접근할 수 있는 권한을 나타내는 문자열입니다. 일반적으로 짧은 유효 기간(보통 몇 분 ~ 몇 시간)을 가지며, API 요청 시에 Authorization 헤더에 포함되어 사용자 인증을 대신합니다. 주로 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]JWT&lt;/span&gt;(JSON Web Token) 형식을 사용합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;갱신 토큰(Refresh Token)은 접근 토큰이 만료되었을 때 새로운 접근 토큰을 발급받기 위해 사용하는 특별한 토큰입니다. 접근 토큰보다 긴 유효 기간(몇 일 ~ 몇 주)을 가지며, 서버 데이터베이스에 안전하게 저장됩니다. 이 방식으로 사용자는 자주 재로그인할 필요 없이 장기간 인증 상태를 유지할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]JWT&lt;/span&gt;: 당사자 간에 정보를 안전하게 전송하기 위한 개방형 표준(RFC7519)입니다. Header(알고리즘 정보) &amp;amp; Payload(클레임 데이터) &amp;amp; Signature(서명) 세 부분으로 구성되며 각 부분은 Base64로 인코딩되어 점(.)으로 구분됩니다. JWT에는 &lt;span style=&quot;color: #009a87;&quot;&gt;필요한 모든 정보(사용자 ID &amp;amp; 권한... 등)를 토큰 자체에 포함&lt;/span&gt;하므로 서버 측에서는 세션 상태를 유지할 필요가 없습니다. 그리고 서명 메커니즘은 발행자가 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]비밀 키&lt;/span&gt;로 서명하여 토큰의 무결성을 보장합니다. 수신자는 서명을 검증해 토큰이 변조되지 않았는지 확인합니다. 주로 사용자 인증 &amp;amp; API 권한 부여 &amp;amp; 서비스 간 통신에 활용됩니다. 단점으로는 &lt;span style=&quot;color: #ee2323;&quot;&gt;토큰의 크기가 커질 수 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;* &lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[2]비밀 키&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;: JWT에서 사용하는 비밀키는 대칭 키와 비대칭 키처럼 암호화가 아닌 서명을 뜻합니다. JWT의 내용(Payload)은 누구나 읽을 수 있지만, &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;변조는 불가능&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;합니다. 서버만 비밀키를 알고 있으므로, 변조된 JWT는 서명 검증에 실패합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Flutter 앱에서 저장된 JWT를 사용해 요청하는 경우
final token = await storage.read(key: 'jwt_token');
final response = await http.get(
&amp;nbsp;&amp;nbsp;Uri.parse('https://api.example.com/data'),
&amp;nbsp;&amp;nbsp;headers: {'Authorization': 'Bearer $token'},
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Flutter에서 토큰 사용 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class AuthService {
&amp;nbsp;&amp;nbsp;final Dio _dio = Dio();
&amp;nbsp;&amp;nbsp;final FlutterSecureStorage _storage = FlutterSecureStorage();
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 로그인 및 JWT 저장
&amp;nbsp;&amp;nbsp;Future&amp;lt;bool&amp;gt; login(String username, String password) async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final response = await _dio.post(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'https://api.example.com/login',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data: {'username': username, 'password': password},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 서버로부터 받은 토큰 저장
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await _storage.write(key: 'access_token', value: response.data['access_token']);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await _storage.write(key: 'refresh_token', value: response.data['refresh_token']);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 요청에 JWT 포함하기
&amp;nbsp;&amp;nbsp;Future&amp;lt;Response&amp;gt; authenticatedRequest(String url) async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String? token = await _storage.read(key: 'access_token');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return _dio.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;options: Options(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers: {'Authorization': 'Bearer $token'},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 토큰 갱신 처리
&amp;nbsp;&amp;nbsp;Future&amp;lt;bool&amp;gt; refreshToken() async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String? refreshToken = await _storage.read(key: 'refresh_token');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final response = await _dio.post(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'https://api.example.com/refresh',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data: {'refresh_token': refreshToken},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await _storage.write(key: 'access_token', value: response.data['access_token']);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;12. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;TCP와 UDP에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ TCP(Transmission Control Protocol)는 연결 지향적이고 신뢰성 있는 데이터 전송을 보장하는 반면, UDP(User Datagram Protocol)는 비연결 지향적이고 빠른 전송 속도를 우선시하는 프로토콜입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;TCP(Transmission Control Protocol)는 연결 지향적 프로토콜로, 데이터 전송 전에 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]3-way handshake&lt;/span&gt;를 통해 연결을 설정합니다. 데이터의 순서 보장 &amp;amp; 손실된 패킷 재전송 &amp;amp; 흐름 제어 및 혼잡 제어 기능을 제공하여 신뢰성 있는 데이터 전송을 보장합니다. 주로 웹 브라우징(HTTP/HTTPS), 이메일(SMTP), 파일 전송(FTP)... 등 &lt;span style=&quot;color: #009a87;&quot;&gt;데이터 무결성이 중요한 애플리케이션에 사용&lt;/span&gt;됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;UDP(User Datagram Protocol)는 비연결 지향적 프로토콜로, 연결 설정 없이 데이터를 전송합니다. 패킷 순서나 도착 보장이 없어서 일부 데이터가 손실될 수 있지만, 오버헤드가 적어 TCP보다 빠릅니다. 주로 실시간 스트리밍, 온라인 게임, DNS 조회와 같이 &lt;span style=&quot;color: #009a87;&quot;&gt;속도가 중요하고 일부 데이터 손실이 허용되는 애플리케이션에 적합&lt;/span&gt;합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Flutter에서는 대부분 HTTP/HTTPS(TCP 기반)를 통해 서버와 통신하지만, 실시간 기능이 필요한 경우 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]WebSocket&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(TCP)&lt;/span&gt;과 &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]WebRTC&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(UDP)&lt;/span&gt;를 사용할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]3-way handshake&lt;/span&gt;: TCP 연결을 설정하는 과정으로, 클라이언트와 서버 간에 신뢰성 있는 연결을 보장하기 위한 3단계 과정입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. SYN(Synchronize)&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;클라이언트 -&amp;gt; 서버: SYN, seq=x&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;클라이언트가 서버에 연결 요청을 보냅니다. 이때 시퀀스 번호(x)를 함께 보냅니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;2. SYN-ACK(Synchronize-Acknowledge)&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;서버 -&amp;gt; 클라이언트: SYN, seq=y, ACK=x+1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;서버가 클라이언트의 요청을 받고 응답합니다. 서버는 클라이언트의 시퀀스 번호(x)에 1을 더한 값을 ACK로, 자신의 시퀀스 번호(y)를 SYN으로 보냅니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;3. ACK(Acknowledge)&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;클라이언트 -&amp;gt; 서버: ACK=y+1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;클라이언트가 서버의 응답을 확인하고 최종 확인 메시지를 보냅니다. 서버의 시퀀스 번호(y)에 1을 더한 값을 ACK로 보냅니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;위 3단계 과정이 완료되면 TCP 연결이 설정되고 데이터 전송이 시작됩니다. 연결 종료 시에는 비슷한 4-way handshake 과정을 거칩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]WebSocket&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Flutter에서 WebSocket 사용 예
final channel = WebSocketChannel.connect(Uri.parse('wss://example.com/ws'));
channel.sink.add('Hello!');
channel.stream.listen((message) {
&amp;nbsp;&amp;nbsp;print('Received: $message');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● TCP 기반의 양방향 통신 프로토콜입니다.&lt;br /&gt;● 단일 TCP 연결을 통해 서버와 클라이언트 간 지속적인 양방향 통신을 제공합니다.&lt;br /&gt;● HTTP 프로토콜 위에서 작동하며, 초기 핸드셰이크를 HTTP로 시작하여 WebSocket 프로토콜로 업그레이드합니다.&lt;br /&gt;● 실시간 채팅 &amp;amp; 알림 &amp;amp; 실시간 대시보드... 등에 적합합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]WebRTC(Web Real-Time Communication)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Flutter에서 WebRTC 사용 예시 (간략화)
final _localRenderer = RTCVideoRenderer();
final _peerConnection = await createPeerConnection({'iceServers': []});

// 로컬 비디오 스트림 설정
final _localStream = await navigator.mediaDevices.getUserMedia({
&amp;nbsp;&amp;nbsp;'audio': true, 'video': true
});
_localRenderer.srcObject = _localStream;

// 피어 연결에 스트림 추가
_localStream.getTracks().forEach((track) {
&amp;nbsp;&amp;nbsp;_peerConnection.addTrack(track, _localStream);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 주로 UDP 기반으로 작동하는 P2P(Peer-to-Peer) 통신 기술입니다.&lt;br /&gt;● 브라우저나 앱 간 직접적인 미디어(오디오, 비디오) 및 데이터 공유가 가능합니다.&lt;br /&gt;● 서버를 통하지 않고 직접 통신하므로 지연 시간이 짧습니다.&lt;br /&gt;● 영상 통화 &amp;amp; 화면 공유 &amp;amp; P2P 파일 전송... 등에 적합합니다.&lt;br /&gt;● 연결 설정을 위한 시그널링 서버는 필요하지만, 데이터 전송은 P2P로 이루어집니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;13. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Git의 Merge와 Rebase의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ Merge는 두 브랜치의 변경 사항을 하나로 합치는 비파괴적 방식이고, Rebase는 한 브랜치의 커밋들을 다른 브랜치 위로 재배치하여 선형적 히스토리를 만드는 방식입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Git Merge는 두 브랜치의 변경 사항을 하나로 통합하는 과정으로, 두 브랜치의 최신 커밋을 가리키는 새로운 병합 커밋을 생성합니다. 원본 브랜치 구조와 모든 커밋 히스토리가 그대로 유지되어 비파괴적인 방식입니다. 주로 협업 환경에서 안전하게 사용하지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;복잡한 병합 히스토리가 생길 수도 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Git Rebase는 한 브랜치의 커밋들을 다른 브랜치의 끝으로 이동시키는 방식으로, 커밋 히스토리를 재작성합니다. 이는 선형적이고 깔끔한 커밋 히스토리를 만들어 주지만, 이미 공유된 브랜치에 적용하면 문제가 발생할 수 있습니다. 주로 로컬에서 작업 중인 개인 브랜치에 적합하며, &lt;span style=&quot;color: #ee2323;&quot;&gt;충돌 해결이 더 복잡할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Merge 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A---B---C (dev)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /
D---E---F---G (main)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;여기서 dev 브랜치를 main으로 merge를 하게되면&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A---B---C (dev)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; \
D---E---F---G---H (main)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;H라는 병합 커밋이 생성되며, 두 브랜치의 변경사항을 모두 포함합니다. 원래의 모든 커밋 히스토리가 보존됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Rebase 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A---B---C (dev)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /
D---E---F---G (main)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;여기서 dev 브랜치에서 main으로 rebase를 하게되면&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A'--B'--C' (dev)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /
D---E---F---G (main)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;결과적으로 dev 브랜치의 커밋들(A, B, C)이 main 브랜치의 끝(G 이후)에 재배치됩니다. 이때 원래의 커밋들은 새로운 커밋(A', B', C')으로 다시 만들어집니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Merge 사용 사례&lt;/span&gt;&lt;br /&gt;● 팀원들과 공유하는 브랜치에 통합할 때 (ex. dev -&amp;gt; feature)&lt;br /&gt;● 작업 내역을 명확하게 분리하고 싶을 때&lt;br /&gt;● 안전하게 브랜치를 합치고 싶을 때&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Rebase 사용 사례&lt;/span&gt;&lt;br /&gt;● 개인 작업 브랜치를 최신 main과 동기화할 때&lt;br /&gt;● 깔끔한 선형 커밋 히스토리를 원할 때&lt;br /&gt;● PR(Pull Request)을 제출하기 전에 커밋을 정리할 때&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;14. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;GraphQL에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ GraphQL은 클라이언트가 필요한 데이터만 정확히 요청할 수 있게 해주는 쿼리 언어입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;GraphQL은 Facebook에서 개발한 API를 위한 쿼리 언어입니다. 클라이언트가 필요한 데이터의 구조를 정확히 지정하여 요청할 수 있게 해줍니다. 단일 엔드포인트를 사용하며, 클라이언트는 쿼리(데이터 조회) &amp;amp; 뮤테이션(데이터 변경) &amp;amp; 서브 스크립션(실시간 데이터)을 통해 서버와 통신합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;REST API와 달리 클라이언트가 정확히 필요한 데이터만 요청할 수 있어서 &lt;span style=&quot;color: #009a87;&quot;&gt;오버페칭(불필요한 데이터 전송)과 언더페칭(여러 API 호출 필요)을 방지&lt;/span&gt;합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;15. C&lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;I/CD에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ CI/CD는 코드 변경 사항을 자동으로 빌드 &amp;amp; 테스트하고 배포까지 자동화하여 개발 프로세스를 가속화하고 품질을 향상시키는 방법론입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;CI(지속적 통합)는 개발자들이 코드 변경 사항을 공유 레포지토리에 자주 병합하고, 자동화된 빌드 및 테스트를 통해 통합 문제를 조기에 발견하는 개발 방식입니다. 이를 통해 &lt;span style=&quot;color: #009a87;&quot;&gt;버그를 빠르게 발견하고 코드 품질을 유지&lt;/span&gt;할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;CD는 지속적 배포(Continuous Deployment)와 지속적 제공(Continuous Delivery)을 의미합니다. 지속적 제공은 코드 변경 사항이 테스트를 통과하면 자동으로 &lt;span style=&quot;color: #009a87;&quot;&gt;스테이징 환경까지 배포&lt;/span&gt;하고, 지속적 배포는 한 단계 더 나아가 &lt;span style=&quot;color: #009a87;&quot;&gt;프로덕션 환경까지 자동으로 배포&lt;/span&gt;하는 것을 의미합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;16. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;선언형 &amp;amp; 명령형 &amp;amp; 함수형 프로그래밍에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 선언형은 '무엇을(What)'에 중점을 두며, 명령형은 '어떻게(How)'에 중점을 두고, 함수형은 순수 함수와 불변 데이터를 사용해 사이드 이펙트를 최소화합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;선언형 프로그래밍은 원하는 결과를 명시하고 실행 방법은 시스템에 맡기는 방식입니다. Flutter의 UI 구현 방식이 대표적인 선언형 접근법으로, 위젯을 통해 화면에 보여질 내용을 선언합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;명령형 프로그래밍은 프로그램의 상태를 변경하는 명령문을 순차적으로 실행하는 방식입니다. 코드가 실행되는 방법과 단계를 명시적으로 정의합니다. C &amp;amp; Java의 전통적인 방식이 여기에 해당합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;함수형 프로그래밍은 선언형의 일종으로, 순수 함수와 불변 데이터를 사용하여 상태 변경과 부작용을 최소화합니다. 핵심 개념으로는 순수 함수(같은 입력에 항상 같은 출력) &amp;amp; 고차 함수(함수를 인자로 받거나 반환) &amp;amp; 불변성이 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Flutter에서는 위의 세 가지 패러다임을 모두 활용합니다. 위젯 구성은 선언형, 상태 관리는 함수형 접근법을 사용하며, 필요에 따라 명령형 코드도 작성할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;선언형 프로그래밍 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Flutter에서 위젯 구성 예시
Widget buildList() {
&amp;nbsp;&amp;nbsp;return ListView(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;children: [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ListTile(title: Text('항목 1')),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ListTile(title: Text('항목 2')),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ListTile(title: Text('항목 3')),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;],
&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 코드는 '무엇을(what)' 화면에 보여줄지 선언합니다. 리스트를 어떻게 그릴지에 대한 세부 과정은 Flutter 프레임워크에 위임하고, 개발자는 원하는 결과물의 구조만 정의합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;명령형 프로그래밍 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;List&amp;lt;int&amp;gt; getEvenNumbers(List&amp;lt;int&amp;gt; numbers) {
&amp;nbsp;&amp;nbsp;List&amp;lt;int&amp;gt; result = [];
&amp;nbsp;&amp;nbsp;for (int i = 0; i &amp;lt; numbers.length; i++) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (numbers[i] % 2 == 0) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.add(numbers[i]);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;return result;
}

// 사용 예:
void main() {
&amp;nbsp;&amp;nbsp;List&amp;lt;int&amp;gt; numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
&amp;nbsp;&amp;nbsp;List&amp;lt;int&amp;gt; evenNumbers = getEvenNumbers(numbers);
&amp;nbsp;&amp;nbsp;print(evenNumbers); // [2, 4, 6, 8, 10]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 코드는 '어떻게(how)' 짝수를 찾는지 명시적으로 단계별로 지시합니다. 반복문을 사용해 각 요소를 확인하고, 조건에 맞으면 결과 리스트에 추가하는 과정을 직접 제어합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;함수형 프로그래밍 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;List&amp;lt;int&amp;gt; getEvenNumbers(List&amp;lt;int&amp;gt; numbers) {
&amp;nbsp;&amp;nbsp;return numbers.where((number) =&amp;gt; number % 2 == 0).toList();
}

// 고차 함수 활용 예시
List&amp;lt;int&amp;gt; applyToEach(List&amp;lt;int&amp;gt; numbers, int Function(int) transform) {
&amp;nbsp;&amp;nbsp;return numbers.map(transform).toList();
}

// 사용 예:
void main() {
&amp;nbsp;&amp;nbsp;List&amp;lt;int&amp;gt; numbers = [1, 2, 3, 4, 5];
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 짝수만 필터링
&amp;nbsp;&amp;nbsp;final evenNumbers = getEvenNumbers(numbers);
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 각 숫자를 제곱
&amp;nbsp;&amp;nbsp;final squared = applyToEach(numbers, (x) =&amp;gt; x * x);
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;print(evenNumbers); // [2, 4]
&amp;nbsp;&amp;nbsp;print(squared); // [1, 4, 9, 16, 25]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;함수형 프로그래밍은 함수를 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]일급 객체&lt;/span&gt;로 취급하고, where &amp;amp; map &amp;amp; fold와 같은 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]고차 함수&lt;/span&gt;를 사용해 데이터 변환을 추상화합니다. 코드가 간결해지고 부작용(side effect)이 최소화됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]일급 객체&lt;/span&gt;: 다른 객체들에 일반적으로 적용할 수 있는 연산을 모두 지원하는 객체를 말합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 함수를 변수에 할당
var greet = (String name) =&amp;gt; 'Hello, $name!';

// 함수를 인자로 전달
void executeFunction(Function callback) {
&amp;nbsp;&amp;nbsp;callback();
}

// 함수를 반환값으로 사용
Function createMultiplier(int factor) {
&amp;nbsp;&amp;nbsp;return (int number) =&amp;gt; number * factor;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Dart에서 함수는 일급 객체입니다.&lt;br /&gt;함수가 일급 객체라는 것은 다음과 같은 특성을 가집니다.&lt;br /&gt;● 변수에 할당할 수 있습니다.&lt;br /&gt;● 다른 함수의 인자로 전달할 수 있습니다.&lt;br /&gt;● 함수의 반환값으로 사용할 수 있습니다.&lt;br /&gt;● 자료구조(리스트, 맵... 등)에 저장할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]고차 함수&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 함수를 인자로 받는 고차 함수
void forEach(List&amp;lt;int&amp;gt; numbers, void Function(int) action) {
&amp;nbsp;&amp;nbsp;for (var number in numbers) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;action(number);
&amp;nbsp;&amp;nbsp;}
}

// 함수를 반환하는 고차 함수
Function(int) createAdder(int addBy) {
&amp;nbsp;&amp;nbsp;return (int x) =&amp;gt; x + addBy;
}

// 사용 예시
void main() {
&amp;nbsp;&amp;nbsp;// 리스트의 map, where, reduce 등도 모두 고차 함수입니다
&amp;nbsp;&amp;nbsp;List&amp;lt;int&amp;gt; numbers = [1, 2, 3, 4, 5];
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// map: 각 요소를 변환하는 함수를 인자로 받음
&amp;nbsp;&amp;nbsp;var doubled = numbers.map((n) =&amp;gt; n * 2).toList();
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// where: 조건 함수를 인자로 받음
&amp;nbsp;&amp;nbsp;var evens = numbers.where((n) =&amp;gt; n % 2 == 0).toList();
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 직접 만든 고차 함수 사용
&amp;nbsp;&amp;nbsp;forEach(numbers, (n) =&amp;gt; print('Number: $n'));
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 함수를 반환하는 고차 함수 사용
&amp;nbsp;&amp;nbsp;var addFive = createAdder(5);
&amp;nbsp;&amp;nbsp;print(addFive(10)); // 15 출력
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;고차 함수는 다음 중 하나 이상의 조건을 만족하는 함수입니다.&lt;br /&gt;● 하나 이상의 함수를 인자로 받는 함수&lt;br /&gt;● 함수를 결과로 반환하는 함수&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;17. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;메모리 누수(Memory Leak)에 대한 설명과 방지하는 방법에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ 메모리 누수는 더 이상 필요하지 않은 객체가 여전히 참조되어 &lt;span style=&quot;color: #006dd7;&quot;&gt;[1]가비지 컬렉션&lt;/span&gt;(GC)에 의해 제거되지 않는 상황으로, Flutter에서는 주로 StatefulWidget의 dispose 메소드를 통해 리소스를 적절히 해제해야 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;메모리 누수란 프로그램이 더 이상 사용하지 않는 메모리를 해제하지 않아 지속적으로 메모리 사용량이 증가하는 현상입니다. Dart는 가비지 컬렉션을 사용하지만, 참조가 유지되면 메모리가 해제되지 않습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* Flutter에서의 주요 메모리 누수 원인&lt;/span&gt;&lt;br /&gt;1. 구독(Subscription)이나 리스너를 취소하지 않는 경우&lt;br /&gt;2. 비동기 작업이 완료되기 전에 위젯이 dispose될 때 &lt;span style=&quot;color: #006dd7;&quot;&gt;[3, 4]컨텍스트&lt;/span&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;유출&lt;/span&gt;&lt;br /&gt;3. 전역 또는 정적 변수에 대한 참조 관리 미흡&lt;br /&gt;4. 대용량 객체를 캐시에 저장하고 해제하지 않는 경우&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;* 방지 방법&lt;/span&gt;&lt;br /&gt;1. dispose 메소드에서 모든 리스너, 컨트롤러, 구독을 취소&lt;br /&gt;2. 비동기 작업에 mounted 체크 추가&lt;br /&gt;3. StreamControllor &amp;amp; AnimationController... 등의 자원 해제&lt;br /&gt;4. 필요시 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]약한 참조&lt;/span&gt;(weak reference) 사용&lt;br /&gt;5. DevTools의 메모리 프로파일러를 활용한 정기적인 메모리 사용량 분석&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이러한 방법을 통해 Dart의 가비지 컬렉션(GC)이 불필요한 객체를 효과적으로 수거할 수 있도록 하여 메모리 누수를 방지할 수 있습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[1] 가비지 컬렉션(Garbage Collection)&lt;/span&gt;: 프로그램이 더 이상 사용하지 않는 메모리를 자동으로 식별하고 해제하는 메모리 관리 시스템입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;동작 원리&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 마킹(Marking): GC는 '루트'객체(ex. 전역 변수 &amp;amp; 스택의 로컬 변수)에서 시작하여 모든 참조를 추적하고 '살아있는'객체로 표시합니다.&lt;br /&gt;2. 스위핑(Sweeping): 표시되지 않은 객체(즉, 더 이상 참조되지 않는 객체)의 메모리를 해제합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;Dart의 가비지 컬렉션&lt;/span&gt;: Dart는 두 세대 가비지 컬렉션 시스템을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;void garbageCollectionExample() {
&amp;nbsp;&amp;nbsp;// 객체가 생성됨
&amp;nbsp;&amp;nbsp;var list = List.filled(1000, 'item');
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 함수가 종료되면 list 변수는 스코프를 벗어나고
&amp;nbsp;&amp;nbsp;// 참조가 없어지므로 가비지 컬렉터가 메모리를 해제함
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;void garbageCollectionExample2() {
&amp;nbsp;&amp;nbsp;// 객체 생성
&amp;nbsp;&amp;nbsp;var largeList = List.filled(10000, 'data');
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 다른 작업 수행
&amp;nbsp;&amp;nbsp;doSomething();
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 이 시점에서 largeList는 더 이상 참조되지 않으므로 
&amp;nbsp;&amp;nbsp;// 다음 GC 사이클에서 메모리가 해제됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;1. 새로운 세대(Young Generation): 새로 생성된 객체는 여기에 할당되며, 빠른 가비지 컬레션이 자주 실행됩니다.&lt;br /&gt;2. 오래된 세대(Old Generation): 여러 가비지 컬렉션 사이클 동안 살아남은 객체가 이동하며, 덜 자주 수행되는 더 철저한 GC가 실행됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[2] 약한 참조(Weak Reference)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import 'dart:core';

void weakReferenceExample() {
&amp;nbsp;&amp;nbsp;// Expando는 객체에 대한 약한 참조 맵을 제공
&amp;nbsp;&amp;nbsp;final expando = Expando&amp;lt;String&amp;gt;('데이터 저장소');
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 테스트용 객체
&amp;nbsp;&amp;nbsp;var obj = Object();
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 객체에 데이터 연결 (약한 참조)
&amp;nbsp;&amp;nbsp;expando[obj] = '중요한 데이터';
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 데이터 접근
&amp;nbsp;&amp;nbsp;print(expando[obj]); // '중요한 데이터' 출력
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// obj에 대한 강한 참조를 제거하면
&amp;nbsp;&amp;nbsp;obj = null;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 다음 GC 사이클 후에 expando[obj]는 접근할 수 없게 됨
&amp;nbsp;&amp;nbsp;// 원래 obj 객체와 연결된 데이터가 메모리에서 해제됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;약한 참조는 객체에 대한 참조이지만, GC가 메모리를 회수하는 것을 방해하지 않는 참조 유형입니다.&lt;br /&gt;Dart에서는 'dart:core'의 'Expando'클래스를 사용하여 약한 참조를 구현할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;참조의 종류와 특징&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 강한 참조(Strong Reference): 일반적인 참조로, 객체가 이러한 참조로 접근 가능한 동안에는 GC가 메모리를 회수하지 않습니다.&lt;br /&gt;2. 약한 참조(Weak Reference): GC가 객체의 메모리를 회수할 수 있으며, 다른 강한 참조가 없는 경우 약한 참조가 있어도 객체가 수거됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]컨텍스트(Context)&lt;/span&gt;: Flutter에서 'Context'는 일반적으로 BuildContext 객체를 의미하며, 위젯 트리에서 현재 위젯의 위치를 나타냅니다. 이는 부모 위젯에 접근하고, 테마 &amp;amp; 미디어 쿼리 &amp;amp; 라우팅... 등과 같은 상위 수준의 기능에 접근하는 데 사용됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ BuildContext 특징&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class MyWidget extends StatelessWidget {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;Widget build(BuildContext context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// context를 사용하여 테마 데이터 접근
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final theme = Theme.of(context);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// context를 사용하여 화면 크기 접근
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final screenSize = MediaQuery.of(context).size;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// context를 사용하여 다른 화면으로 이동
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return ElevatedButton(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;child: Text('다음 화면'),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPressed: () {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Navigator.of(context).push(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaterialPageRoute(builder: (context) =&amp;gt; NextScreen())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;● 위젯의 위치 정보 포함&lt;br /&gt;● 상위 위젯 찾기(dependOnInheritedWidgetOfExactType)를 위한 방법 제공&lt;br /&gt;● 테마 &amp;amp; 네비게이션 &amp;amp; 스낵바 &amp;amp; 모달... 등을 위한 접근점 역할&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[4]컨텍스트 유출(Context Leak)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class LeakingWidget extends StatefulWidget {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;_LeakingWidgetState createState() =&amp;gt; _LeakingWidgetState();
}

class _LeakingWidgetState extends State&amp;lt;LeakingWidget&amp;gt; {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;void initState() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.initState();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 비동기 작업 시작
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchDataWithDelay().then((data) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 위젯이 이미 트리에서 제거된 후에도 setState 시도 가능
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// -&amp;gt; 컨텍스트 유출 및 &quot;setState() called after dispose()&quot; 에러
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setState(() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 데이터 업데이트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;Future&amp;lt;String&amp;gt; fetchDataWithDelay() async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 3초 후 데이터 반환
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await Future.delayed(Duration(seconds: 3));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return &quot;데이터&quot;;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;Widget build(BuildContext context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Container();
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;컨텍스트 유출은 위젯이 dispose된 후에도 해당 위젯의 BuildContext를 계속 사용하려고 시도하는 상황을 말합니다. 이는 메모리 누수와 예기치 않은 앱 크래시를 일으킬 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #8a3db6;&quot;&gt;발생 원인&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 비동기 작업 후 상태 업데이트 시도: 위젯이 삭제된 후 완료되는 비동기 작업에서 setState 호출&lt;br /&gt;2. 이벤트 리스너/콜백에 컨텍스트 캡처: 위젯이 삭제된 후에도 활성 상태로 유지되는 리스너에서 컨텍스트 사용&lt;br /&gt;3. 전역 싱글톤에 컨텍스트 저장: 애플리케이션 생명주기 동안 컨텍스트를 유지하는 전역 객체&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #8a3db6;&quot;&gt;컨텍스트 유출(Context Leak) 방지 방법&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 취소 가능한 작업 사용&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class CancellableWidget extends StatefulWidget {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;_CancellableWidgetState createState() =&amp;gt; _CancellableWidgetState();
}

class _CancellableWidgetState extends State&amp;lt;CancellableWidget&amp;gt; {
&amp;nbsp;&amp;nbsp;// 취소 토큰
&amp;nbsp;&amp;nbsp;CancelToken? _cancelToken;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;void initState() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.initState();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_cancelToken = CancelToken();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 취소 가능한 작업 시작
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchDataWithCancellation(_cancelToken!).then((data) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (mounted) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setState(() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 데이터 업데이트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).catchError((e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 취소된 경우 처리
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (e is CancelledException) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print('작업이 취소되었습니다');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;void dispose() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 작업 취소
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_cancelToken?.cancel();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.dispose();
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;Widget build(BuildContext context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Container();
&amp;nbsp;&amp;nbsp;}
}

// 취소 토큰 클래스
class CancelToken {
&amp;nbsp;&amp;nbsp;bool _isCancelled = false;
&amp;nbsp;&amp;nbsp;bool get isCancelled =&amp;gt; _isCancelled;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;void cancel() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_isCancelled = true;
&amp;nbsp;&amp;nbsp;}
}

class CancelledException implements Exception {}

Future&amp;lt;String&amp;gt; fetchDataWithCancellation(CancelToken token) async {
&amp;nbsp;&amp;nbsp;for (int i = 0; i &amp;lt; 3; i++) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 각 단계에서 취소 여부 확인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (token.isCancelled) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw CancelledException();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await Future.delayed(Duration(seconds: 1));
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;return &quot;데이터&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;2.&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;[5]mounted&lt;/span&gt;&amp;nbsp;체크&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class SafeWidget extends StatefulWidget {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;_SafeWidgetState createState() =&amp;gt; _SafeWidgetState();
}

class _SafeWidgetState extends State&amp;lt;SafeWidget&amp;gt; {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;void initState() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.initState();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchDataWithDelay().then((data) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// mounted 체크로 안전하게 상태 업데이트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (mounted) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setState(() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 데이터 업데이트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;Future&amp;lt;String&amp;gt; fetchDataWithDelay() async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await Future.delayed(Duration(seconds: 3));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return &quot;데이터&quot;;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;Widget build(BuildContext context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Container();
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[5]mounted&lt;/span&gt;: Flutter의 State 객체에서 제공하는 Boolean 속성입니다. 이 속성은 현재 State 객체가 위젯 트리에 연결되어 있는지를 나타냅니다.&lt;br /&gt;● true: State 객체가 현재 위젯 트리에 연결되어 있고, setState를 호출할 수 있는 상태&lt;br /&gt;● false: State 객체가 위젯 트리에서 제거되었으며(위젯이 dispose된 후), setState를 호출하면 안 되는 상태&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #8a3db6;&quot;&gt;생명주기와 관련된 mounted 상태 변화&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class MyWidget extends StatefulWidget {
&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;_MyWidgetState createState() =&amp;gt; _MyWidgetState();
}

class _MyWidgetState extends State&amp;lt;MyWidget&amp;gt; {
&amp;nbsp;&amp;nbsp;String _data = 'Loading...';

&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;void initState() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.initState();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 비동기 작업 시작
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchData().then((result) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 비동기 작업이 완료된 시점에 위젯이 여전히 트리에 있는지 확인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (mounted) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setState(() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_data = result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 이미 dispose된 상태라면 setState를 호출하지 않음
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print('위젯이 이미 dispose되었습니다.');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;Future&amp;lt;String&amp;gt; fetchData() async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await Future.delayed(Duration(seconds: 2));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return '데이터 로드 완료!';
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@override
&amp;nbsp;&amp;nbsp;Widget build(BuildContext context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Text(_data);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;1. createState() 호출: State 객체가 생성됩니다.&lt;br /&gt;2. initState() 호출: 이 시점에 'mounted = true'가 됩니다.&lt;br /&gt;3. 위젯이 화면에서 제거&lt;br /&gt;4. dispose() 호출: 이후 'mounted = false'가 됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;18. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;BDD &amp;amp; TDD의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ TDD(Test-Driven Development)는 개발자가 구현 전 테스트를 먼저 작성하는 개발 방법론이고, BDD(Behavior-Driven Development)는 TDD를 확장하여 비즈니스 요구사항과 행동에 초점을 맞춘 접근법입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt; TDD(Test-Driven Development) &lt;/span&gt;&lt;br /&gt;● 테스트 코드를 먼저 작성한 후 실제 코드를 구현하는 방식입니다.&lt;br /&gt;● 레드(실패) =&amp;gt; 그린(성공) =&amp;gt; 리팩터(개선)의 사이클로 진행합니다.&lt;br /&gt;● 개발자 중심적이며 기술적 관점에서 코드의 정확성을 검증합니다.&lt;br /&gt;● Flutter에서는 'flutter_test' 라이브러리를 사용하여 단위 테스트와 위젯 테스트를 작성합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt; BDD(Behavior-Driven Development) &lt;/span&gt;&lt;br /&gt;● TDD의 확장으로, 사용자 스토리와 비즈니스 요구사항 중심입니다.&lt;br /&gt;● Given(상황) =&amp;gt; When(행동) =&amp;gt; Then(결과) 형식으로 테스트를 작성합니다.&lt;br /&gt;● 개발자뿐만 아니라 비개발자(기획자 &amp;amp; 디자이너 &amp;amp; 사용자)도 이해할 수 있는 자연어로 명세합니다.&lt;br /&gt;● Flutter에서는 'flutter_gherkin' 라이브러리를 통해 BDD 스타일의 테스트 작성이 가능합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;실무 적용 차이&lt;br /&gt;● TDD는 주로 단위 테스트에 집중되어 있고 기술적 세부 사항을 다룹니다.&lt;br /&gt;● BDD는 전체 기능의 동작과 사용자 스토리에 초점을 맞추며 통합 테스트나 인수 테스트와 더 관련이 있습니다.&lt;br /&gt;● Flutter 앱 개발에서는 두 방법론을 혼합하여 사용하는 것이 효과적입니다. 위젯 테스트는 BDD 스타일로, 비즈니스 로직은 TDD로 진행합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;19. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;SDK 개발과 서비스 개발의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ SDK 개발은 다른 개발자가 사용할 도구와 라이브러리를 만드는 것으로 안정적인 API와 호환성에 중점을 두며, 서비스 개발은 최종 사용자를 위한 애플리케이션을 구축하는 것으로 사용자 경험과 기능 구현에 초점을 맞춥니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;SDK(Software Development Kit) 개발은 다른 개발자들이 자신의 애플리케이션에 통합할 수 있는 &lt;span style=&quot;color: #009a87;&quot;&gt;도구 &amp;amp; API &amp;amp; 라이브러리 세트를 개발&lt;/span&gt;합니다.&lt;br /&gt;● SDK는 &quot;한 번 작성하고 어디서나 사용&quot;을 목표로 하며, 호환성과 안전성이 최우선입니다.&lt;br /&gt;● SDK는 다른 개발자를 대상으로 하므로 API 설계와 문서화에 더 많은 시간을 투자합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;SDK 개발 시, 고려 사항&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 안정적이고 직관적인 API 설계&lt;br /&gt;2. 철저한 문서화와 샘플 코드 제공&lt;br /&gt;3. 버전 간 호환성과 명확한 버전 관리&lt;br /&gt;4. 다양한 환경에서 일관된 동작&lt;br /&gt;5. 불필요한 종속성 최소화&lt;br /&gt;&amp;nbsp;&lt;br /&gt;서비스(Application) 개발은 최종 사용자를 위한 &lt;span style=&quot;color: #009a87;&quot;&gt;애플리케이션 구축&lt;/span&gt;을 말합니다.&lt;br /&gt;● 서비스는 특정 사용자 니즈 충족을 목표로 하며, 사용자 경험과 차별화된 기능이 중요합니다.&lt;br /&gt;● 서비스는 최종 사용자를 대상으로 하므로 UI/UX와 비즈니스 로직에 집중합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;서비스 개발 시, 고려 사항&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 사용자 경험(UX)과 인터페이스(UI) 디자인&lt;br /&gt;2. 비즈니스 로직과 기능 구현&lt;br /&gt;3. 성능 최적화와 사용자 피드백 반영&lt;br /&gt;4. 배포 및 업데이트 전략&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;20. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;HTTP와 HTTPS의 차이점에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ HTTP는 암호화되지 않은 평문 통신 프로토콜이지만, HTTPS는 SSL/TLS를 통해 데이터를 암호화하여 안전한 통신을 제공합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;HTTP(HyperText Transfer Protocol)&lt;/span&gt;&lt;br /&gt;● 웹 브라우저와 서버 간의 통신을 위한 기본 프로토콜&lt;br /&gt;● 텍스트 기반의 평문(암호화되지 않은) 통신&lt;br /&gt;● 기본 포트: 80&lt;br /&gt;● 장점: 단순하고 빠름, 적은 오버헤드&lt;br /&gt;● 단점: 데이터 노출 위험, 중간자 공격에 취약&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;HTTPS(HTTP Secure)&lt;/span&gt;&lt;br /&gt;● HTTP에 SSL/TLS(보안 소켓 계층/전송 계층 보안) 프로토콜을 추가한 것&lt;br /&gt;● 암호화된 통신으로 데이터 보호&lt;br /&gt;● 동작 방식: 공개키/개인키 암호화 및 디지털 인증서 사용&lt;br /&gt;● 기본 포트: 443&lt;br /&gt;● 장점&lt;br /&gt;1. 데이터 암호화로 중요 정보 보호&lt;br /&gt;2. 서버 인증을 통한 신뢰성 확보&lt;br /&gt;3. 데이터 무결성 검증 가능&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Flutter 앱 개발 시, 고려 사항&lt;/span&gt;&lt;br /&gt;● Android 9(API 레벨 28) 이상부터는 기본적으로 HTTP 통신을 차단합니다. HTTPS 사용이 필수입니다.&lt;br /&gt;● IOS에서는 ATS(App Transport Security)로 HTTPS 사용이 필수입니다.&lt;br /&gt;● Flutter에서 HTTP 요청 시, 'http' 라이브러리를 사용합니다.&lt;br /&gt;● HTTP 사용 시, 안드로이드 Manifest에 cleartext 통신 허용 설정이 필요합니다.&lt;br /&gt;● 프로덕션 앱에서는 HTTPS 사용이 필수입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333;&quot;&gt;21. &lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;HTTPS의 SSL Handshaking에 대해 말해주세요.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;└ SSL/TLS Handshaking은 클라이언트와 서버가 안전한 통신을 위해 서로를 인증하고 암호화 방식을 협상하여 공유 세션 키를 생성하는 과정입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;◎ &lt;span style=&quot;color: #5733b1;&quot;&gt;SSL/TLS Handshaking 과정&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 클라이언트 헬로(Client Hello): 클라이언트가 지원하는 암호화 스위트 &amp;amp; 랜덤 데이터 &amp;amp; 프로토콜 버전... 등을 서버에 전송합니다.&lt;br /&gt;2. 서버 헬로(Server Hello): 서버가 선택한 암호화 스위트 &amp;amp; 랜덤 데이터 &amp;amp; SSL 인증서를 클라이언트에 전송합니다.&lt;br /&gt;3. 인증서 검증: 클라이언트가 서버의 인증서를 신뢰할 수 있는지 확인합니다. (CA 검증)&lt;br /&gt;4. 키 교환: 클라이언트가 임시 대칭 키(세션 키) 생성 후, 서버의 공개키로 암호화하여 전송합니다.&lt;br /&gt;5. 세션 키 생성 완료: 양측이 동일한 세션 키를 보유하게 됩니다.&lt;br /&gt;6. 암호화 통신 시작: 이후 모든 통신은 협상된 세션 키를 사용한 대칭 암호화로 통신합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;하이브리드 암호화로 HTTPS 이해하기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이해를 돕기 위해, 위의 9번 &quot;대칭 키와 비대칭 키&quot;에 이어서 추가 설명을 하겠습니다.&lt;/span&gt;&lt;br /&gt;HTTPS는 가장 대표적인 하이브리드 암호화 사용 사례입니다.&lt;br /&gt;HTTPS의 하이브리드 암호화는 두 가지 단계로 구분할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 핸드셰이크 단계&lt;br /&gt;● 클라이언트가 서버에 연결 요청&lt;br /&gt;● 서버가 인증서(공개 키 포함)를 클라이언트에 전송&lt;br /&gt;● 클라이언트가 임시 대칭 키(세션 키)를 생성하고 서버의 공개 키로 암호화&lt;br /&gt;● 암호화된 세션 키를 서버에 전송&lt;br /&gt;● 서버가 자신의 개인 키로 세션 키를 복호화&lt;br /&gt;&amp;nbsp;&lt;br /&gt;2. 데이터 교환 단계&lt;br /&gt;● 이후 모든 데이터 통신은 합의된 세션 키를 사용한 대칭 암호화(주로 AES)로 진행&lt;br /&gt;● 이 방식은 비대칭 암호화의 보안성과 대칭 암호화의 효율성을 모두 제공&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이러한 SSL/TLS 핸드셰이킹 과정을 통해 HTTPS는 안전한 데이터 통신을 보장하며, 중요한 정보가 오가는 모바일 앱에서는 필수적인 보안 요소입니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;긴&amp;nbsp;글을&amp;nbsp;끝까지&amp;nbsp;읽어주셔서&amp;nbsp;감사합니다! &lt;br /&gt;&lt;br /&gt;개념을 정리하면서, 특히 메모리 관리나 비동기 프로그래밍과 같은 기초적이지만 중요한 개념들을 다시 한번 깊이 생각해 볼 수 있었습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그럼, 다음 포스트에서 인사드리겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <category>flutter 개념 정리</category>
      <category>flutter 개발자 면접</category>
      <category>개발자 인터뷰</category>
      <category>개발자 취업 준비</category>
      <category>기술 면접 준비</category>
      <category>디자인 패턴</category>
      <category>모바일 개발 지식</category>
      <category>소트프웨어 아키텍처</category>
      <category>클린 아키텍처</category>
      <category>프로그래밍 기초</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/572</guid>
      <comments>https://kwonputer.tistory.com/572#entry572comment</comments>
      <pubDate>Tue, 4 Mar 2025 06:35:18 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 면접 질문을 통해 알아보는 플러터 (1) - 목차</title>
      <link>https://kwonputer.tistory.com/571</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;안녕하세요!&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;최근에 Kotlin만 다뤄서 벌써부터 Flutter에 대한 지식이 흐릿해지더라구요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(사람의 기억력은 어쩔 수 없나 봅니다..)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;그래서 오늘은! Flutter에 대해서 알아보고 기억을 되새겨 보려고 하는데요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;이제는 저도 나이가 조금 차서 그런지, 내심 마음속으로 단순 암기의 반복은 재미가 없고 리소스를 투자할 이유도 없다는 생각을 하면서 암기를 해버리니까 효율이 너무 떨어졌습니다ㅠ&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;그래서 좀 더 재밌는 방법을 찾다가 생각한 게 &quot;면접 질문으로 공부하자!&quot;였습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;면접 질문에는 기본적이고 핵심적인 질문과 경험에 대해 물어보는 질문이 많으니까 재밌을 것 같았어요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;물론, 재미뿐만 아니라 차후에 이번 포스트가 저에게 도움이 될 거라고 생각하는 부분도 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;사담은 여기까지 하고, 목차에 대한 포스트를 작성해 볼게요.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;&lt;i&gt;큰 범위의 개념에 대한 질문&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;알고 있는 디자인 패턴에 대해서 말해주세요.&lt;/li&gt;
&lt;li&gt;MVC, MVP, MVVM 패턴의 차이점을 말해주세요.&lt;/li&gt;
&lt;li&gt;싱글톤 디자인 패턴의 장점과 단점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;클린 아키텍처(Clean Architecture)를 반드시 사용해야 하는 이유를 말해주세요.&lt;/li&gt;
&lt;li&gt;의존성 역전에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;스레드와 프로세스, 멀티 스레드와 멀티 프로세스에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;이미지 캐시(Memory, Disk)에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;해시(Hash)에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;대칭 키와 비대칭 키에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;동기(Synchronous)와 비동기(Asynchronous)에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;접근 토큰(Access Token)과 갱신 토큰(Refresh Token)에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;TCP와 UDP에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Git의 Merge와 Rebase의 차이점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;GraphQL에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;CI/CD에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;선언형 &amp;amp; 명령형 &amp;amp; 함수형 프로그래밍에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;메모리 누수(Memory Leak)에 대한 설명과 방지하는 방법에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;BDD &amp;amp; TDD의 차이점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;SDK 개발과 서비스 개발의 차이점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;HTTP와 HTTPS의 차이점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;HTTPS의 SSL Handshaking에 대해 말해주세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/572&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741331359223&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Flutter] 면접 질문을 통해 알아보는 플러터 (2) - 개념&quot; data-og-description=&quot;https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 &quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/572&quot; data-og-url=&quot;https://kwonputer.tistory.com/572&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hDPy9/hyYqL9UyUd/UChcBRIRcS4FOQEsCww3kK/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284,https://scrap.kakaocdn.net/dn/Y0JO9/hyYm0nkua7/3QbbLmYYkglkto5kiK1Ga1/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/572&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hDPy9/hyYqL9UyUd/UChcBRIRcS4FOQEsCww3kK/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284,https://scrap.kakaocdn.net/dn/Y0JO9/hyYm0nkua7/3QbbLmYYkglkto5kiK1Ga1/img.png?width=690&amp;amp;height=1284&amp;amp;face=0_0_690_1284');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Flutter] 면접 질문을 통해 알아보는 플러터 (2) - 개념&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술 &amp;amp; 경험※ 아직 포스트를 작성하는 중입니다.&amp;nbsp;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;span style=&quot;color: #006dd7;&quot;&gt; &lt;b&gt;&lt;i&gt;Flutter 기술 질문&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Flutter의 장점과 단점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Stateless &amp;amp; Stateful Widget의 차이점을 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter에서 상태 관리는 어떻게 하는지 말해주세요.&lt;/li&gt;
&lt;li&gt;Riverpod에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Bloc에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Provider에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;GetX에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;위젯 트리 구조에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter에서 비동기 프로그래밍을 하는 방법에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter에서 API 호출을 하는 방법에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Hot Reload와 Hot Restart의 차이점을 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter에서 Native 코드와의 통합 방법에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter에서 라우팅과 네비게이션을 처리하는 방법에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter의 Form 위젯과 Form 검증 방법에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Build Context의 context의 역할에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter의 Key에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter의 Stream과 Stream 유형에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;FutureBuilder와 StreamBuilder의 차이점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Flutter의 3가지 테스트(단위 테스트, 위젯 테스트, 통합 테스트)에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;WidgetsApp과 MaterialApp의 차이점에 대해 말해주세요.&lt;/li&gt;
&lt;li&gt;Abstract (extends) &amp;amp; Interface (implements) &amp;amp; Mixin (with)에 대해 말해주세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/573&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/573&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741331343756&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Flutter] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술&quot; data-og-description=&quot;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.https://kwonputer.tistory.com/572&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (2) - 개념https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/573&quot; data-og-url=&quot;https://kwonputer.tistory.com/573&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxVEM5/hyYqavdoTP/L8xk6mR5hcNKcMMdG7FYq1/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997,https://scrap.kakaocdn.net/dn/sKnvG/hyYqS8189F/py5SJzWQxXVEt2kkRRdF81/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/573&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/573&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxVEM5/hyYqavdoTP/L8xk6mR5hcNKcMMdG7FYq1/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997,https://scrap.kakaocdn.net/dn/sKnvG/hyYqS8189F/py5SJzWQxXVEt2kkRRdF81/img.png?width=800&amp;amp;height=997&amp;amp;face=0_0_800_997');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Flutter] 면접 질문을 통해 알아보는 플러터 (3) - Flutter 기술&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;※ 아래의 '개념'에 대한 포스트를 읽고 오시면 이해하기 더 쉽습니다.https://kwonputer.tistory.com/572&amp;nbsp;[ Flutter ] 면접 질문을 통해 알아보는 플러터 (2) - 개념https://kwonputer.tistory.com/573&amp;nbsp;[ Flutter ] 면접 질&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&lt;i&gt;경험에 대한 질문&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개발을 진행하면서 어려움을 느꼈던 순간과 어려움을 해결하기 위한 노력을 말해주세요.&lt;/li&gt;
&lt;li&gt;코드 리뷰를 할 때, 중점적으로 보는 것은 어떤 것인지 말해주세요.&lt;/li&gt;
&lt;li&gt;팀원간의 갈등 상황을 어떻게 해결하는지 말해주세요.&lt;/li&gt;
&lt;li&gt;학습하는 방법과 정보를 얻는 채널을 말해주세요.&lt;/li&gt;
&lt;li&gt;개발자가 된 이유와 개발 직군에서 모바일 개발을 선택한 이유를 말해주세요.&lt;/li&gt;
&lt;li&gt;최근에 읽은 책이나 기억에 남는 글이 있다면 말해주세요.&lt;/li&gt;
&lt;li&gt;좋은 코드에 대한 생각을 말해주세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/574&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/574&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741331326307&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Flutter] 면접 질문을 통해 알아보는 플러터 (4) - 경험&quot; data-og-description=&quot;안녕하세요!이번 포스트에서는 '경험'에 대한 면접 질문에 대해서 다루겠습니다.실제 면접이라고 생각하고 포스트를 작성해 보겠습니다.주관적인 생각을 바탕으로 작성한 포스트라서 참고만 &quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/574&quot; data-og-url=&quot;https://kwonputer.tistory.com/574&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kM4TZ/hyYnd72DA2/FQrQFPuJ0f33D2nFnAshEK/img.png?width=800&amp;amp;height=1049&amp;amp;face=0_0_800_1049,https://scrap.kakaocdn.net/dn/dVFrJV/hyYqaorWbT/CkYhchRLkQxVyS1IMQR9EK/img.png?width=800&amp;amp;height=1049&amp;amp;face=0_0_800_1049&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/574&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/574&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kM4TZ/hyYnd72DA2/FQrQFPuJ0f33D2nFnAshEK/img.png?width=800&amp;amp;height=1049&amp;amp;face=0_0_800_1049,https://scrap.kakaocdn.net/dn/dVFrJV/hyYqaorWbT/CkYhchRLkQxVyS1IMQR9EK/img.png?width=800&amp;amp;height=1049&amp;amp;face=0_0_800_1049');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Flutter] 면접 질문을 통해 알아보는 플러터 (4) - 경험&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요!이번 포스트에서는 '경험'에 대한 면접 질문에 대해서 다루겠습니다.실제 면접이라고 생각하고 포스트를 작성해 보겠습니다.주관적인 생각을 바탕으로 작성한 포스트라서 참고만&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;앞으로 '개념 &amp;amp; 기술 &amp;amp; 경험'에 대한 포스트를 추가로 작성할 예정입니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;그럼, 다음 포스트에서 인사드리겠습니다~!&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <category>flutter 개발자</category>
      <category>flutter 면접 질문</category>
      <category>flutter 아키텍처</category>
      <category>flutter 프레임워크</category>
      <category>kotlin과 flutter</category>
      <category>개발자 준비</category>
      <category>모바일 앱개발</category>
      <category>상태관리</category>
      <category>테크 인터뷰</category>
      <category>프로그래밍 면접</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/571</guid>
      <comments>https://kwonputer.tistory.com/571#entry571comment</comments>
      <pubDate>Mon, 3 Mar 2025 21:41:17 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 퀴즈 만들기 개발</title>
      <link>https://kwonputer.tistory.com/570</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;※ 최근 일정이 너무 많아서 바빠졌습니다ㅠㅠ 여유가 생기면 바로 다음 개발 진행하겠습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740638303795&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mgH3L/hyYmSOOMYX/DNI9ATLlkL9fxk45kmV1Tk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/eygyV/hyYjAbdd6P/xMCozFVNq016DOkiFkU83K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mgH3L/hyYmSOOMYX/DNI9ATLlkL9fxk45kmV1Tk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/eygyV/hyYjAbdd6P/xMCozFVNq016DOkiFkU83K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/569&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/569&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741076910635&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[초성마켓] 프로젝트 중간 리뷰&quot; data-og-description=&quot;https://github.com/KwonGeneral/chosungmarket.git&amp;nbsp;GitHub - KwonGeneral/chosungmarket: 초성마켓초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.github.com&amp;nbsp;안녕하세요~!이번 포스트에서는 프&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/569&quot; data-og-url=&quot;https://kwonputer.tistory.com/569&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKJPbr/hyYmJrF5Sj/Ok9oFDMQjEZou4Nobz0rzk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/S3MVm/hyYm6NPxfU/LJObO18dKKFiBVRqCvW0NK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dTe3pE/hyYmM9LCU5/coIqdKOO0UsmRRyor3w7AK/img.png?width=877&amp;amp;height=1321&amp;amp;face=0_0_877_1321&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/569&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/569&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKJPbr/hyYmJrF5Sj/Ok9oFDMQjEZou4Nobz0rzk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/S3MVm/hyYm6NPxfU/LJObO18dKKFiBVRqCvW0NK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dTe3pE/hyYmM9LCU5/coIqdKOO0UsmRRyor3w7AK/img.png?width=877&amp;amp;height=1321&amp;amp;face=0_0_877_1321');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[초성마켓] 프로젝트 중간 리뷰&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&amp;nbsp;GitHub - KwonGeneral/chosungmarket: 초성마켓초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.github.com&amp;nbsp;안녕하세요~!이번 포스트에서는 프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 퀴즈 만들기 페이지를 개발하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 기능보다는 프론트 작업이라서 정보 전달을 해드릴게 많이 없네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당분간은 대략적인 작업 흐름정도만 올릴 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250227_151037013_04.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eNlq6M/btsMys95IJg/gJZ6ZN9YUQM9wTfVKz9NHK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eNlq6M/btsMys95IJg/gJZ6ZN9YUQM9wTfVKz9NHK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eNlq6M/btsMys95IJg/gJZ6ZN9YUQM9wTfVKz9NHK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeNlq6M%2FbtsMys95IJg%2FgJZ6ZN9YUQM9wTfVKz9NHK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;1025&quot; data-filename=&quot;KakaoTalk_20250227_151037013_04.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 이미지는 기존의 퀴즈 만들기 페이지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번에 많은 데이터를 입력 받는 디자인에서 단계를 나눠서 데이터를 입력받도록 변경해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mdUmq/btsMxIr3fCE/mGoJqp7nQvEGsfuFwLLHj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mdUmq/btsMxIr3fCE/mGoJqp7nQvEGsfuFwLLHj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mdUmq/btsMxIr3fCE/mGoJqp7nQvEGsfuFwLLHj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmdUmq%2FbtsMxIr3fCE%2FmGoJqp7nQvEGsfuFwLLHj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;523&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPnzMM/btsMxEXHdiQ/0Cxqx2s5mAMqsewkhCvbL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPnzMM/btsMxEXHdiQ/0Cxqx2s5mAMqsewkhCvbL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPnzMM/btsMxEXHdiQ/0Cxqx2s5mAMqsewkhCvbL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPnzMM%2FbtsMxEXHdiQ%2F0Cxqx2s5mAMqsewkhCvbL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;390&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 난이도의 경우에도 퀴즈 개별마다 선택할 수 있도록 수정하고, 태그 기능도 추가해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QuizData, QuizGroupData 데이터 클래스에서 미리 만들어둔 필드인 'tagList, difficulty' 필드를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, QuizCreatePage에서 해당 페이지에서 사용할 퀴즈 폼 data class를 만들어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fnjzb/btsMx8RAIb3/9ztfNYAg8RAMV785aEpETK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fnjzb/btsMx8RAIb3/9ztfNYAg8RAMV785aEpETK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fnjzb/btsMx8RAIb3/9ztfNYAg8RAMV785aEpETK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFnjzb%2FbtsMx8RAIb3%2F9ztfNYAg8RAMV785aEpETK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;407&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 퀴즈 유효성 검증 함수를 만들건데요, 규칙은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;초성 검증&lt;/b&gt;: 자음만 있어야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;정답 검증&lt;/b&gt;: 초성과 글자 수가 같아야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;2차 검증&lt;/b&gt;: 초성이 자음만 존재하고, 초성과 정답의 글자 수가 같다면, 각 글자의 첫 자음이 초성과 일치하는지 확인.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;1196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b702aN/btsMx2RrEaZ/VbSFYtHKloam6cKagyLu7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b702aN/btsMx2RrEaZ/VbSFYtHKloam6cKagyLu7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b702aN/btsMx2RrEaZ/VbSFYtHKloam6cKagyLu7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb702aN%2FbtsMx2RrEaZ%2FVbSFYtHKloam6cKagyLu7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;1196&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;1196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5GIKa/btsMxB7Friu/6cgt6t4gtZg4lPhYklzhek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5GIKa/btsMxB7Friu/6cgt6t4gtZg4lPhYklzhek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5GIKa/btsMxB7Friu/6cgt6t4gtZg4lPhYklzhek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5GIKa%2FbtsMxB7Friu%2F6cgt6t4gtZg4lPhYklzhek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;926&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자음 검증을 위해서 한글 유니코드를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고나서, QuizCreatePageViewModel에서 퀴즈 폼을 추가, 수정, 삭제하는 기능과 퀴즈 그룹을 추가하는 기능을 만들어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;1161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7mvva/btsMxBmjYe6/hFTuBpOZD7A0TUNZX3IefK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7mvva/btsMxBmjYe6/hFTuBpOZD7A0TUNZX3IefK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7mvva/btsMxBmjYe6/hFTuBpOZD7A0TUNZX3IefK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7mvva%2FbtsMxBmjYe6%2FhFTuBpOZD7A0TUNZX3IefK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;1161&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;1161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;1214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xjFGz/btsMyFORS3l/9ag2a7NwdSZeiNpkugnDL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xjFGz/btsMyFORS3l/9ag2a7NwdSZeiNpkugnDL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xjFGz/btsMyFORS3l/9ag2a7NwdSZeiNpkugnDL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxjFGz%2FbtsMyFORS3l%2F9ag2a7NwdSZeiNpkugnDL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;969&quot; height=&quot;1214&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;1214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈를 추가할 때, 검증 함수를 활용해주고, State를 활용해서 View Error 핸들링도 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 퀴즈의 태그를 모아서 중복을 제거하고, 제거한 태그 목록을 QuizGroupData의 tagList 필드에 넣겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는, 퀴즈 페이지를 단계별로 진행할 수 있게 Index 변수를 하나 만들고, 뒤로가기 시에, Index가 줄어들고 페이지가 전환되도록 개발하겠습니다. 그리고 State가 Error인 경우에는 Toast를 띄우고, Success인 경우에는 홈페이지로 이동하도록 하면 될 것 같네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;1297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yupTC/btsMxkZuMjP/LPbWk5S7JHKQZSKp48VSc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yupTC/btsMxkZuMjP/LPbWk5S7JHKQZSKp48VSc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yupTC/btsMxkZuMjP/LPbWk5S7JHKQZSKp48VSc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyupTC%2FbtsMxkZuMjP%2FLPbWk5S7JHKQZSKp48VSc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;1297&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;1297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 폼 추가 시, 유효성 검증 후에, 통과하면 currentQuizIndex를 1 증가시키고, 화면을 다음 퀴즈 추가화면으로 전환되도록 QuizCreatePage에 함수도 하나 만들겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zSiOD/btsMw4JcUdY/BDEaKbi22tT2O1aqw4VkQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zSiOD/btsMw4JcUdY/BDEaKbi22tT2O1aqw4VkQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zSiOD/btsMw4JcUdY/BDEaKbi22tT2O1aqw4VkQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzSiOD%2FbtsMw4JcUdY%2FBDEaKbi22tT2O1aqw4VkQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;766&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 폼 생성 화면에서, 태그의 경우에는 특수문자가 불가능하도록 제어하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIof6s/btsMwBAncH6/86jBDOlxkZCITEjqc0GbPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIof6s/btsMwBAncH6/86jBDOlxkZCITEjqc0GbPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIof6s/btsMwBAncH6/86jBDOlxkZCITEjqc0GbPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIof6s%2FbtsMwBAncH6%2F86jBDOlxkZCITEjqc0GbPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;971&quot; height=&quot;504&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 디자인을 쭉쭉 밀어주면 퀴즈 만들기 페이지 개발도 끝입니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DZlLA/btsMw25JAFl/Tdu3nHYZ3M0NFP809D63B0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DZlLA/btsMw25JAFl/Tdu3nHYZ3M0NFP809D63B0/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250227_151037013.jpg&quot; width=&quot;400&quot; height=&quot;1025&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DZlLA/btsMw25JAFl/Tdu3nHYZ3M0NFP809D63B0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDZlLA%2FbtsMw25JAFl%2FTdu3nHYZ3M0NFP809D63B0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaBu7Z/btsMyUFd8rr/j88SGiFsBUrDUKUhPrWhU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaBu7Z/btsMyUFd8rr/j88SGiFsBUrDUKUhPrWhU1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250227_151037013_01.jpg&quot; width=&quot;400&quot; height=&quot;1025&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaBu7Z/btsMyUFd8rr/j88SGiFsBUrDUKUhPrWhU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaBu7Z%2FbtsMyUFd8rr%2Fj88SGiFsBUrDUKUhPrWhU1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqfxR5/btsMzqRj3Mk/KqdujVncYusegY0EVSUuTk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqfxR5/btsMzqRj3Mk/KqdujVncYusegY0EVSUuTk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250227_151037013_02.jpg&quot; width=&quot;400&quot; height=&quot;1025&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqfxR5/btsMzqRj3Mk/KqdujVncYusegY0EVSUuTk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqfxR5%2FbtsMzqRj3Mk%2FKqdujVncYusegY0EVSUuTk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZ2Ms/btsMysvv41u/fO8gFCT1kDKWxPspITKhp0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZ2Ms/btsMysvv41u/fO8gFCT1kDKWxPspITKhp0/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250227_151037013_03.jpg&quot; width=&quot;400&quot; height=&quot;1025&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZ2Ms/btsMysvv41u/fO8gFCT1kDKWxPspITKhp0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZ2Ms%2FbtsMysvv41u%2FfO8gFCT1kDKWxPspITKhp0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 내 정보 페이지 개발을 할 예정인데, 시간이 남는다면 홈 페이지도 손을 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈페이지에서 태그 필터를 제거하고, 상단에 랭킹같은걸 표시해주는게 더 좋아보이더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 이정도만 하고 마무리하겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;디테일은 나중에 캐릭터 이미지 작업을 진행하고나서 잡아야겠네요.&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/570</guid>
      <comments>https://kwonputer.tistory.com/570#entry570comment</comments>
      <pubDate>Thu, 27 Feb 2025 14:33:13 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 프로젝트 중간 리뷰</title>
      <link>https://kwonputer.tistory.com/569</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740556956487&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Oyqbl/hyYjBHK7l5/ysL8Tmwr5EVzfGL7yRLMi1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/btbHLD/hyYjExIizt/nMSYinvTTiVUa19UP882sk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Oyqbl/hyYjBHK7l5/ysL8Tmwr5EVzfGL7yRLMi1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/btbHLD/hyYjExIizt/nMSYinvTTiVUa19UP882sk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 프로젝트에 대해서 짧게 리뷰를 해보려고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘이, 프로젝트를 2월에 시작해서 대략 3주정도 지난 시점이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 진행하기 전에는, Flutter와 Python을 더 많이 다뤘었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Kotlin의 감을 잃지 않기 위해서 이번 프로젝트는 Kotlin으로 진행하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 제가 기존에 개발하던 방식이 아닌 최신 기술들을 접목시켜서 개발해보는 방향으로 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;아래의 링크를 참고하시면 더 좋습니다.&lt;/i&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/551&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/551&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740554746441&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kotlin] Flutter로 비교하는 Kotlin &amp;amp; Compose 지식&quot; data-og-description=&quot;https://github.com/KwonGeneral/chosungmarket.git&amp;nbsp;GitHub - KwonGeneral/chosungmarket: 초성마켓초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.github.com&amp;nbsp;&amp;nbsp;이번 포스트에서는 몇 개월만에 Ko&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/551&quot; data-og-url=&quot;https://kwonputer.tistory.com/551&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cZSLbo/hyYjldVGKU/wMGkbkmSzUMi3fUhhOCkbK/img.png?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640,https://scrap.kakaocdn.net/dn/rttS2/hyYjpOb8jc/duL2CGnumMc9hFxOlBidkk/img.png?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640,https://scrap.kakaocdn.net/dn/El605/hyYjw0P1YZ/8vbfVR6LtB8230gE7kzpK0/img.png?width=566&amp;amp;height=579&amp;amp;face=0_0_566_579&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/551&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/551&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cZSLbo/hyYjldVGKU/wMGkbkmSzUMi3fUhhOCkbK/img.png?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640,https://scrap.kakaocdn.net/dn/rttS2/hyYjpOb8jc/duL2CGnumMc9hFxOlBidkk/img.png?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640,https://scrap.kakaocdn.net/dn/El605/hyYjw0P1YZ/8vbfVR6LtB8230gE7kzpK0/img.png?width=566&amp;amp;height=579&amp;amp;face=0_0_566_579');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Kotlin] Flutter로 비교하는 Kotlin &amp;amp; Compose 지식&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&amp;nbsp;GitHub - KwonGeneral/chosungmarket: 초성마켓초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.github.com&amp;nbsp;&amp;nbsp;이번 포스트에서는 몇 개월만에 Ko&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/541&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/541&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740554769290&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[초성마켓] 클린 아키텍처 적용 (1) - 설명&quot; data-og-description=&quot;오늘은 클린 아키텍처에 대해서 제가 느낀점을 포스팅해볼 생각입니다~&amp;nbsp;먼저 클린 아키텍처는 사실 2가지 개념이 중요하고, 개발 방법은 사람마다 다를 수도 있다고 생각합니다. 중요한 2가지 &quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/541&quot; data-og-url=&quot;https://kwonputer.tistory.com/541&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d2fVzT/hyYjEqV5zx/2B9Ssb0jAHrPBolCICaKNK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/eGUwr/hyYjmDWzW9/KlyEcjV73tC3pSXN0qHv41/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/FXIi1/hyYjrd8l4h/nFXU6aQlqJ1W0Mikeh3YuK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/541&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/541&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d2fVzT/hyYjEqV5zx/2B9Ssb0jAHrPBolCICaKNK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/eGUwr/hyYjmDWzW9/KlyEcjV73tC3pSXN0qHv41/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/FXIi1/hyYjrd8l4h/nFXU6aQlqJ1W0Mikeh3YuK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[초성마켓] 클린 아키텍처 적용 (1) - 설명&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 클린 아키텍처에 대해서 제가 느낀점을 포스팅해볼 생각입니다~&amp;nbsp;먼저 클린 아키텍처는 사실 2가지 개념이 중요하고, 개발 방법은 사람마다 다를 수도 있다고 생각합니다. 중요한 2가지&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/553&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/553&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740554780086&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kotlin] 문법 - 반복문 &amp;amp; 배열 &amp;amp;  해시 &amp;amp; 정렬&quot; data-og-description=&quot;오늘은 Kotlin 문법에 대해서 다뤄보려고 합니다.저번에 코딩테스트를 한 번 해봤는데, Kotlin 문법을 까먹었더니 확장함수를 쓸 수가 없어서 너무 불편하더라고요, 그래서 이번 포스트를 작성하게&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/553&quot; data-og-url=&quot;https://kwonputer.tistory.com/553&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wUipq/hyYjwmeuJL/8Zf9kBdjC2durFZr9krCsK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/AEZUD/hyYjvnjEQj/dRkSZmnREbmTGZQn50kn0K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/553&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/553&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wUipq/hyYjwmeuJL/8Zf9kBdjC2durFZr9krCsK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/AEZUD/hyYjvnjEQj/dRkSZmnREbmTGZQn50kn0K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Kotlin] 문법 - 반복문 &amp;amp; 배열 &amp;amp; 해시 &amp;amp; 정렬&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 Kotlin 문법에 대해서 다뤄보려고 합니다.저번에 코딩테스트를 한 번 해봤는데, Kotlin 문법을 까먹었더니 확장함수를 쓸 수가 없어서 너무 불편하더라고요, 그래서 이번 포스트를 작성하게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;toml&lt;/b&gt;&lt;/span&gt; 파일에 대해서 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 최신 버전, Compose 등을 사용해서 개발을 진행했었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 최신 버전을 설치하고 적용하면서 바로 헤맸던 부분이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;1321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnHwY/btsMxCkADr0/HnpFwWNILKpiVd0pLCP6i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnHwY/btsMxCkADr0/HnpFwWNILKpiVd0pLCP6i1/img.png&quot; data-alt=&quot;[1] - toml&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnHwY/btsMxCkADr0/HnpFwWNILKpiVd0pLCP6i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnHwY%2FbtsMxCkADr0%2FHnpFwWNILKpiVd0pLCP6i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;877&quot; height=&quot;1321&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;1321&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[1] - toml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[1]&lt;/span&gt;번 이미지 처럼, toml 파일인데요. 처음에는 못 보던 환경설정 파일이 생겨서 당황했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색하고 알아보니, 버전관리를 중앙 처리하기 위해서 만들어진 파일이더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 버전 관리는 프로젝트 &amp;amp; 앱 &amp;amp; 셋팅 단위의 gradle에서 각 파일별로 다이렉트 코드로 버전을 명시하는 방식이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에, 각 파일별로 버전이 상이하다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;빌드 오류&lt;/span&gt;가 발생하는 경우가 가끔 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 각각의 &lt;span style=&quot;color: #ee2323;&quot;&gt;라이브러리 &amp;amp; 플러그인... 등의 버전을 한번에 파악하기에 어려움&lt;/span&gt;이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주관적으로는 위의 문제를 해결하기 위해서 toml 파일이 만들어졌다고 생각하고 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;Jetpack Compose &amp;amp; 함수형 프로그래밍&lt;/span&gt;에 관한 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에도 Compose에 대한 뉴스와 정보는 계속해서 들려왔기 때문에, 해당 기술에 대해서는 인지하고 있던 상황이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 Compose 지식을 습득하고 적용하면서 느낀점은 생각보다 &lt;span style=&quot;color: #ee2323;&quot;&gt;러닝커브(지식 습득 난이도)&lt;/span&gt;가 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 느낀 이유는, 함수형 프로그래밍 방식을 사용하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 기존에도 함수형 프로그래밍을 일부분 적용해서 사용하고, Flutter를 사용한 개발도 했었기에 어려움은 없었지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;처음 개발을 접하시는 분들이 하기에는 난이도&lt;/span&gt;가 있을거라 생각합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uIaOE/btsMx5z45XC/TQcz3l3DlzlNiO4YrXU45K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uIaOE/btsMx5z45XC/TQcz3l3DlzlNiO4YrXU45K/img.png&quot; data-alt=&quot;[2] - MainActivity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uIaOE/btsMx5z45XC/TQcz3l3DlzlNiO4YrXU45K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuIaOE%2FbtsMx5z45XC%2FTQcz3l3DlzlNiO4YrXU45K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;132&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[2] - MainActivity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VOclH/btsMwYBKA2v/wz0C64v3A5r62ouuixcy0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VOclH/btsMwYBKA2v/wz0C64v3A5r62ouuixcy0k/img.png&quot; data-alt=&quot;[3] - MainActivity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VOclH/btsMwYBKA2v/wz0C64v3A5r62ouuixcy0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVOclH%2FbtsMwYBKA2v%2Fwz0C64v3A5r62ouuixcy0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;828&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[3] - MainActivity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 &lt;span style=&quot;color: #006dd7;&quot;&gt;[2] &amp;amp; [3]&lt;/span&gt;번의 이미지는 MainActivity의 코드 일부분인데요, &lt;span style=&quot;color: #006dd7;&quot;&gt;[2]&lt;/span&gt;번 이미지의 apply는 Kotlin의 함수형 문법이라고 보시면 되고, &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]&lt;/span&gt;번 이미지는 Compose를 사용한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서는 let, apply, also, run... 등등 다양한 함수형 문법을 지원합니다. 보통은 'let' &amp;amp; 'apply' 2가지를 많이 사용합니다. let의 경우에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;Null 체크&lt;/span&gt;를 하기 위해서 사용하고, apply의 경우에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;객체에 바로 접근&lt;/span&gt;하기 위해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply를 좀 더 설명해드리자면, 특정 class가 있고, 해당 class의 변수가 있다면.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 해당 class를 내부에 선언하고 참조해서 변수의 값을 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 apply를 사용한다면, 선언과 동시에 내부의 값을 수정해서 특정 변수에 담을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2w6IS/btsMyrW7RC7/ereXwkhGhfwk45H7dAq6k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2w6IS/btsMyrW7RC7/ereXwkhGhfwk45H7dAq6k1/img.png&quot; data-alt=&quot;[4-1] apply&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2w6IS/btsMyrW7RC7/ereXwkhGhfwk45H7dAq6k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2w6IS%2FbtsMyrW7RC7%2FereXwkhGhfwk45H7dAq6k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;78&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[4-1] apply&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oJXbd/btsMw3XeTvG/ZqiUIOMZSatmyCkOI9VPwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oJXbd/btsMw3XeTvG/ZqiUIOMZSatmyCkOI9VPwk/img.png&quot; data-alt=&quot;[4-2] apply&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oJXbd/btsMw3XeTvG/ZqiUIOMZSatmyCkOI9VPwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoJXbd%2FbtsMw3XeTvG%2FZqiUIOMZSatmyCkOI9VPwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;228&quot; height=&quot;76&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[4-2] apply&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[4-1] &amp;amp; [4-2]&lt;/span&gt; 이미지 처럼 7번 채널의 tv를 바로 변수에 담을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로, Compose의 경우에도 &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]&lt;/span&gt;번 이미지 처럼 함수형 프로그래밍 방식을 사용하는데요, &lt;b&gt;'innerPadding'&lt;/b&gt; 처럼 Scaffold가 내부에서 사용해야할 파라미터를 함수형 방식으로 받는겁니다. 'innterPadding'처럼 임의로 선언을 하지 않는다면 기본 값은&lt;b&gt; 'it'&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWpIE/btsMysaIy7u/vv9vBl8eYuLUqK7UQscuxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWpIE/btsMysaIy7u/vv9vBl8eYuLUqK7UQscuxK/img.png&quot; data-alt=&quot;[5] Scaffold&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWpIE/btsMysaIy7u/vv9vBl8eYuLUqK7UQscuxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWpIE%2FbtsMysaIy7u%2Fvv9vBl8eYuLUqK7UQscuxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;753&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[5] Scaffold&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[5]&lt;/span&gt;번 이미지는 Scaffold의 코드입니다. content를 보시면 PaddingValues 타입을 받아서 Unit(함수)로 구현하라고 선언이 되어있습니다. 그래서, &lt;span style=&quot;color: #006dd7;&quot;&gt;[3]&lt;/span&gt;번 이미지의 'innterPadding'가 &lt;span style=&quot;color: #ee2323;&quot;&gt;content 파라미터에 주입&lt;/span&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 이해를 돕자면, 이러한 방식은 사용자의 액션에서 많이 사용합니다. 예를 들어, '내 정보' 화면에서 프로필을 눌렀을 때, 다양한 액션이 있을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로필 확대&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로필 편집&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 무반응&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 다양한 액션이 있다고 한다면, 이를 통합 처리하는게 번거로워집니다. 파라미터로 액션에 대한 값을 받아서, 해당 값들을 분기(if, switch)처리를 통해 각각의 기능을 수행해야 할텐데, 이러한 방식은 유지보수에는 유리하겠지만, 확장성이 매우 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에, 많은 사용자들이 사용하고 응용하는 대규모 라이브러리나 프레임워크에서는 특정 클래스 &amp;amp; 함수가 어떤 기능을 수행하는지 정의만 되어있고, 로직(기능)은 비어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저는&lt;span style=&quot;color: #ee2323;&quot;&gt; '프로필 클릭 이벤트'&lt;/span&gt;를 줄게요, 당신은 프로필 클릭 이벤트가 발생했을 때, 해야하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;'로직(기능)을 개발하세요.'&lt;/span&gt;&lt;/b&gt; 라는 뜻으로 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 러닝커브가 있다고 하는 점은, 함수형 프로그래밍 방식을 사용하면 &lt;span style=&quot;color: #009a87;&quot;&gt;장점&lt;/span&gt;으로 &lt;span style=&quot;color: #009a87;&quot;&gt;개발 속도 &amp;amp; 편의성이 많이 높아집니다.&lt;/span&gt; 대신에, &lt;span style=&quot;color: #ee2323;&quot;&gt;단점&lt;/span&gt;으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;코드 리뷰와 유지보수성(디버깅)&lt;/span&gt;이 떨어지고, 잘못된 방식으로 개발할 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;수습이 어렵습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 많은 프로젝트에서 모든 코드를 함수형 프로그래밍으로 개발하는 것이 아닌, 필요에 따라 부분 적용하는 방식을 사용합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;클린 아키텍처(Clean Architecture)&lt;/span&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/541&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/541&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740554883389&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[초성마켓] 클린 아키텍처 적용 (1) - 설명&quot; data-og-description=&quot;오늘은 클린 아키텍처에 대해서 제가 느낀점을 포스팅해볼 생각입니다~&amp;nbsp;먼저 클린 아키텍처는 사실 2가지 개념이 중요하고, 개발 방법은 사람마다 다를 수도 있다고 생각합니다. 중요한 2가지 &quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/541&quot; data-og-url=&quot;https://kwonputer.tistory.com/541&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d2fVzT/hyYjEqV5zx/2B9Ssb0jAHrPBolCICaKNK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/eGUwr/hyYjmDWzW9/KlyEcjV73tC3pSXN0qHv41/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/FXIi1/hyYjrd8l4h/nFXU6aQlqJ1W0Mikeh3YuK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/541&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/541&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d2fVzT/hyYjEqV5zx/2B9Ssb0jAHrPBolCICaKNK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/eGUwr/hyYjmDWzW9/KlyEcjV73tC3pSXN0qHv41/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407,https://scrap.kakaocdn.net/dn/FXIi1/hyYjrd8l4h/nFXU6aQlqJ1W0Mikeh3YuK/img.png?width=296&amp;amp;height=407&amp;amp;face=0_0_296_407');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[초성마켓] 클린 아키텍처 적용 (1) - 설명&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 클린 아키텍처에 대해서 제가 느낀점을 포스팅해볼 생각입니다~&amp;nbsp;먼저 클린 아키텍처는 사실 2가지 개념이 중요하고, 개발 방법은 사람마다 다를 수도 있다고 생각합니다. 중요한 2가지&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biz4At/btsMymhlt0Q/ssaYMUFt8KEGKRQzh75Gf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biz4At/btsMymhlt0Q/ssaYMUFt8KEGKRQzh75Gf0/img.png&quot; data-alt=&quot;[6] Flutter 의존성 역전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biz4At/btsMymhlt0Q/ssaYMUFt8KEGKRQzh75Gf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbiz4At%2FbtsMymhlt0Q%2FssaYMUFt8KEGKRQzh75Gf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;600&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[6] Flutter 의존성 역전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 &lt;span style=&quot;color: #006dd7;&quot;&gt;[6]&lt;/span&gt;번 이미지는 Flutter로 Build 진행을 통해 의존성 역전 시, 만들어지는 'injection.config.dart' 파일입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter에서는 클린 아키텍처를 도입하기 위해서, 의존성 역전 라이브러리인 injectable(&lt;a href=&quot;https://pub.dev/packages/injectable&quot;&gt;https://pub.dev/packages/injectable&lt;/a&gt;)과 getit(&lt;a href=&quot;https://pub.dev/packages/get_it&quot;&gt;https://pub.dev/packages/get_it&lt;/a&gt;)을 적용했었습니다. 위 2개 라이브러리는 Build를 통해, 의존성을 주입합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제 경험을 말씀드리자면, Build 진행 시, 항상 긍정적인 결과가 나오는게 아니었습니다. Android Stuido에서는 내부적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;빌드 캐시&lt;/span&gt;를 만드는데요, 이 때문에 가끔식 Build 진행 시, &lt;span style=&quot;color: #ee2323;&quot;&gt;꼬일때가 있습니다&lt;/span&gt;. 이 부분은 경험이 없다면 생각보다 많은 시간을 투자해서 문제를 해결해야합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9tABH/btsMwrddlpt/iNaqQCp9CQ83J1PxbRmozk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9tABH/btsMwrddlpt/iNaqQCp9CQ83J1PxbRmozk/img.png&quot; data-alt=&quot;[7] Kotlin 의존성 역전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9tABH/btsMwrddlpt/iNaqQCp9CQ83J1PxbRmozk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9tABH%2FbtsMwrddlpt%2FiNaqQCp9CQ83J1PxbRmozk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;671&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[7] Kotlin 의존성 역전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Flutter에 대해서 말씀 드린 이유는, Kotlin의 Koin 라이브러리가 얼마나 편한지를 전달하고 싶었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 &lt;span style=&quot;color: #006dd7;&quot;&gt;[7]&lt;/span&gt;번 이미지를 보시면, Kotlin의 Koin을 사용하면 선언으로 의존성 역전이 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Koin은 &lt;span style=&quot;color: #ee2323;&quot;&gt;런타임 방식&lt;/span&gt;을 사용하는데, 처음에는 디버깅이 어려울 것이라 생각했지만, 막상 적용해보니 아직까지는 어려움을 느끼지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 개발 속도가 빨라져서 만족하면서 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처를 적용하면, 작업량이 기존 프로젝트 개발보다 훨씬 많아집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;703&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXGOIr/btsMyGs0TIV/DZ7ws9M3yZ7BUKDAtqDYK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXGOIr/btsMyGs0TIV/DZ7ws9M3yZ7BUKDAtqDYK1/img.png&quot; data-alt=&quot;[8] 초성마켓 내부 패키지 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXGOIr/btsMyGs0TIV/DZ7ws9M3yZ7BUKDAtqDYK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXGOIr%2FbtsMyGs0TIV%2FDZ7ws9M3yZ7BUKDAtqDYK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;239&quot; height=&quot;703&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;703&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[8] 초성마켓 내부 패키지 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[8]&lt;/span&gt;번 이미지는 &lt;span style=&quot;color: #ee2323;&quot;&gt;초성마켓 내부 패키지 구조&lt;/span&gt;입니다. 간단한 프로젝트인데도 상당히 많은 패키지가 만들어집니다. 그래서, 간단한 프로젝트에서는 클린 아키텍처보다 에자일 방식으로 가볍고 빠르게 개발하는게 좋다고 생각합니다. 클린 아키텍처의 경우에는 어느정도 완성도가 있는 기획 &amp;amp; 시나리오... 등이 필요해서 작은 프로젝트에는 적합하지 않다고 생각합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;라우터(Router)&lt;/span&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddaeLU/btsMyEvjld4/P70YriRYZQpRjoOTIKWxj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddaeLU/btsMyEvjld4/P70YriRYZQpRjoOTIKWxj0/img.png&quot; data-alt=&quot;[9-1] 기존 Kotlin의 Activity 전환&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddaeLU/btsMyEvjld4/P70YriRYZQpRjoOTIKWxj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddaeLU%2FbtsMyEvjld4%2FP70YriRYZQpRjoOTIKWxj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;91&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[9-1] 기존 Kotlin의 Activity 전환&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7wIaF/btsMxmvH5AN/Es7GbIMf7WTR3lJlGUy390/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7wIaF/btsMxmvH5AN/Es7GbIMf7WTR3lJlGUy390/img.png&quot; data-alt=&quot;[9-2] 기존 Kotlin의 Fragment 전환&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7wIaF/btsMxmvH5AN/Es7GbIMf7WTR3lJlGUy390/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7wIaF%2FbtsMxmvH5AN%2FEs7GbIMf7WTR3lJlGUy390%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;469&quot; height=&quot;133&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[9-2] 기존 Kotlin의 Fragment 전환&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[9-1] &amp;amp; [9-2]&lt;/span&gt;번 이미지는 기존 Kotlin의 Activity와 Fragment 전환입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KuX0L/btsMyvkZR6S/afnVRp58Wf71r8FkSH4TEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KuX0L/btsMyvkZR6S/afnVRp58Wf71r8FkSH4TEk/img.png&quot; data-alt=&quot;[10-1] Kotlin Compose 라우터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KuX0L/btsMyvkZR6S/afnVRp58Wf71r8FkSH4TEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKuX0L%2FbtsMyvkZR6S%2FafnVRp58Wf71r8FkSH4TEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;1147&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1147&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10-1] Kotlin Compose 라우터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;965&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ToRfJ/btsMx9JgACf/pRk0KsSE3QeFTqkMCYGLlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ToRfJ/btsMx9JgACf/pRk0KsSE3QeFTqkMCYGLlk/img.png&quot; data-alt=&quot;[10-2] Kotlin Compose 라우터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ToRfJ/btsMx9JgACf/pRk0KsSE3QeFTqkMCYGLlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FToRfJ%2FbtsMx9JgACf%2FpRk0KsSE3QeFTqkMCYGLlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;965&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;965&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10-2] Kotlin Compose 라우터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PdMcu/btsMw07ojZQ/qNN5t437oVTmjYnV9Yd2U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PdMcu/btsMw07ojZQ/qNN5t437oVTmjYnV9Yd2U0/img.png&quot; data-alt=&quot;[10-3] Kotlin Compose에서 라우터 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PdMcu/btsMw07ojZQ/qNN5t437oVTmjYnV9Yd2U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPdMcu%2FbtsMw07ojZQ%2FqNN5t437oVTmjYnV9Yd2U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;737&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;737&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10-3] Kotlin Compose에서 라우터 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[10-1] &amp;amp; [10-2]&lt;/span&gt; 이미지는 Kotlin Compose의 라우터이고, &lt;span style=&quot;color: #006dd7;&quot;&gt;[10-3]&lt;/span&gt; 이미지는 해당 라우터를 사용한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 만약, Flutter를 다루지 않고 기존의 Kotlin만 사용했었다면, 많이 헤맸을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 &lt;span style=&quot;color: #ee2323;&quot;&gt;Flutter와 Compose는 유사한 점이 매우 많습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 라우터 관련해서 &lt;span style=&quot;color: #333333;&quot;&gt;중앙 제어&lt;/span&gt;를 좋아합니다. 그래서 navigate를 바로 사용하는게 아니라, &lt;span style=&quot;color: #006dd7;&quot;&gt;[10-2]&lt;/span&gt;번 처럼 특정 라우터 파일에서 제어하는 것을 선호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 빠른 개발을 위해서 다이렉트 코드로 라우터 전환을 했지만, 차후에 유지보수가 너무 어렵더라구요. 그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;특수한 상황(Push &amp;amp; Location... 등등)&lt;/span&gt;을 제외하고는 중앙 제어를 하려고 노력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 초성마켓 프로젝트에서는 라우터 관련해서 상세하게 개발하지 않고 큰 틀만 잡아놔서, 추가 설명을 드리기 어렵네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 설명 드리기에는 앱 개발에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;라우터&lt;/span&gt;는 정말 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 라우터 관련해서는 다음에 따로 포스트를 작성해보려고 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트는 여기서 마치겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <category>Clean Architecture</category>
      <category>Compose</category>
      <category>Di</category>
      <category>flutter</category>
      <category>kotlin</category>
      <category>router</category>
      <category>의존성</category>
      <category>초성마켓</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/569</guid>
      <comments>https://kwonputer.tistory.com/569#entry569comment</comments>
      <pubDate>Wed, 26 Feb 2025 17:10:44 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 홈 &amp;amp; 퀴즈 기능 및 페이지 개발</title>
      <link>https://kwonputer.tistory.com/568</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740487534423&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 홈 페이지와 퀴즈 페이지를 개발하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 홈 페이지의 디자인 같은 경우 Header를 제거하고, 필터도 텍스트버튼으로 변경하고 리스트도 지금처럼 카드뷰가아닌 구분선을 활용해서 목록처럼 만들생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Impl로 필터 관련해서 기존의 getQuizGroupList의 파라미터를 수정하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXS6go/btsMx5M52IM/ZWLEGh7K0Al1Fxme6JJlAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXS6go/btsMx5M52IM/ZWLEGh7K0Al1Fxme6JJlAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXS6go/btsMx5M52IM/ZWLEGh7K0Al1Fxme6JJlAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXS6go%2FbtsMx5M52IM%2FZWLEGh7K0Al1Fxme6JJlAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;908&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로 퀴즈 그룹 목록을 가져오는 UseCase부터 작업해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 정렬 옵션을 받아서 활용하려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds1KiN/btsMwqxVwTf/ninJUe9XlWTDIm4tKFZjDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds1KiN/btsMwqxVwTf/ninJUe9XlWTDIm4tKFZjDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds1KiN/btsMwqxVwTf/ninJUe9XlWTDIm4tKFZjDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds1KiN%2FbtsMwqxVwTf%2FninJUe9XlWTDIm4tKFZjDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;1084&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 Firebase store에 접근하는 쿼리문을 수정하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;1240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NT3Fg/btsMx8Xfddo/4AeV8f1npkg8Eeh2CtEcc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NT3Fg/btsMx8Xfddo/4AeV8f1npkg8Eeh2CtEcc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NT3Fg/btsMx8Xfddo/4AeV8f1npkg8Eeh2CtEcc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNT3Fg%2FbtsMx8Xfddo%2F4AeV8f1npkg8Eeh2CtEcc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;807&quot; height=&quot;1240&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;1240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고나서 data 레이어의 repository를 작업해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOjdlR/btsMwdMheQ5/KlvpurxJVjnpa6WG6QU9x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOjdlR/btsMwdMheQ5/KlvpurxJVjnpa6WG6QU9x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOjdlR/btsMwdMheQ5/KlvpurxJVjnpa6WG6QU9x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOjdlR%2FbtsMwdMheQ5%2FKlvpurxJVjnpa6WG6QU9x1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;670&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고나서 HomePage로 넘어와서, State 관련해서 파라미터를 추가해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWOEFL/btsMv6TR0lm/kLCvNx3T7f63DcqZKdC3lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWOEFL/btsMv6TR0lm/kLCvNx3T7f63DcqZKdC3lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWOEFL/btsMv6TR0lm/kLCvNx3T7f63DcqZKdC3lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWOEFL%2FbtsMv6TR0lm%2FkLCvNx3T7f63DcqZKdC3lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;697&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 ViewModel의 기능과 화면을 쭉쭉 개발해주면 HomePage는 끝입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 QuizGamePage와 QuizGameResult 페이지를 개발하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2개 페이지는 기능을 수정할 것은 없고 화면만 수정하면 되기 때문에, 쭉쭉 개발해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 나머지는 프론트라서 중요한 건 없지만, 키보드는 중요해서 몇 가지 적어보려고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android에서 키보드는 앱 소속이 아닌, 시스템 소속이기 때문에 접근 및 제어가 쉽지 않습니다. 무엇보다 각 OS별로 동작이 달라질 수도 있습니다. 다만, 제 경험으로는 2가지를 주의하면 어느정도는 커버가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;i&gt;&lt;b&gt;1. 포커스에 따른 직접적인 키보드 Show, Hide 이벤트 처리 주의.&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; (이 부분은 앱의 성능도 떨어뜨리고, 코드 복잡도는 상당히 많이 올라갑니다. 분기처리도 많이 필요하게 될거구요. 그러나, OS에 따라서 작동을 하지 않을 가능성이 매우 높고, 오히려 완성도를 떨어뜨릴 수 있습니다.)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;i&gt;* 그렇기에, 키보드 관련된 부분은 오로지 시스템에 맡기는 것이 좋습니다.&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;i&gt;* 포커스를 제어해서 자연스럽게 키보드의 Show &amp;amp; Hide를 유도하는 것은 좋지만, 직접적으로 키보드를 Show &amp;amp; Hide 하는 것은 오류가 발생할 확률이 높습니다.&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;i&gt;&lt;b&gt;2. 키보드 높이에 따라 위젯을 Position으로 위치 제어 주의.&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; (위와 동일하게 성능 이슈가 있습니다. 저사양 기기로 해당 기능을 수행하게 되면 멈추거나 버벅거림이 심해집니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;i&gt;* Position의 경우 비율을 사용해서 위치를 제어하는 것은 좋으나, 어느정도 노가다를 해야하는 부분이 있습니다. 또한, 최신 기기에서는 문제가 없지만, 저사양 기기에서 실행하면 바로 성능 이슈가 눈에 보이는 경우가 많습니다.&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2가지를 아예 사용하지 않는 것이 아니라, 인지하고 주의해서 사용하면 괜찮다고 생각합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 퀴즈 페이지에서 정답 TextField를 화면 최하단에 그리고, 키보드가 올라오면 키보드 위에 붙어서 올라오는 디자인을 구현하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 AndroidManifest.xml에서 아래 부분을 추가해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbd4WX/btsMwAAn8o2/EOPZeKQTFkqbTvTJ1fK0n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbd4WX/btsMwAAn8o2/EOPZeKQTFkqbTvTJ1fK0n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbd4WX/btsMwAAn8o2/EOPZeKQTFkqbTvTJ1fK0n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbd4WX%2FbtsMwAAn8o2%2FEOPZeKQTFkqbTvTJ1fK0n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;437&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 QuizGamePage로 넘어오셔서, 키보드 관련 변수를 선언해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vIyIn/btsMvgbTAkF/dStrJ33uhpuneZbz0Ik3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vIyIn/btsMvgbTAkF/dStrJ33uhpuneZbz0Ik3Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vIyIn/btsMvgbTAkF/dStrJ33uhpuneZbz0Ik3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvIyIn%2FbtsMvgbTAkF%2FdStrJ33uhpuneZbz0Ik3Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;901&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정답 TextField를 개발해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;1295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AnOs7/btsMu9RugOt/4FnHeVLxDO0UHlBElwRTC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AnOs7/btsMu9RugOt/4FnHeVLxDO0UHlBElwRTC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AnOs7/btsMu9RugOt/4FnHeVLxDO0UHlBElwRTC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAnOs7%2FbtsMu9RugOt%2F4FnHeVLxDO0UHlBElwRTC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;811&quot; height=&quot;1295&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;1295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 처리를 하면, AndroidManifest에서 추가한 옵션으로 인해서, 키보드가 올라와도 화면은 Resize를 하지 않습니다. 즉 Position의 변화가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, TextField의 Modifier에 imePadding() 메소드를 활용해서 키보드의 변화에 따른 패딩을 추적해서 TextField에 적용해주면 끝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Compose를 사용해서 프로젝트를 처음 개발해보는데, 정말 편하네요. 단점으로는 프리뷰가 생각보다 귀찮다는 점인데, 이것도 적응이 되서 그런가 괜찮네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키보드 관련된 처리도 원래는 저렇게 간편하게 되는건 아닙니다. Compose를 사용하니 간편하게 처리를 할 수 있었네요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_00.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL78Ml/btsMw3PQh0Q/NeSOAiBS3BkarBzV07yIt1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL78Ml/btsMw3PQh0Q/NeSOAiBS3BkarBzV07yIt1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL78Ml/btsMw3PQh0Q/NeSOAiBS3BkarBzV07yIt1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL78Ml%2FbtsMw3PQh0Q%2FNeSOAiBS3BkarBzV07yIt1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_00.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_01.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI3TvJ/btsMx1w6TQ0/LhAyRnYl7Lh94t8bZ24090/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI3TvJ/btsMx1w6TQ0/LhAyRnYl7Lh94t8bZ24090/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI3TvJ/btsMx1w6TQ0/LhAyRnYl7Lh94t8bZ24090/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI3TvJ%2FbtsMx1w6TQ0%2FLhAyRnYl7Lh94t8bZ24090%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_01.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_02.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XhQoA/btsMxmodbde/mvhM5MOMKNiWoSDqkTVX4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XhQoA/btsMxmodbde/mvhM5MOMKNiWoSDqkTVX4K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XhQoA/btsMxmodbde/mvhM5MOMKNiWoSDqkTVX4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXhQoA%2FbtsMxmodbde%2FmvhM5MOMKNiWoSDqkTVX4K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_02.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_03.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qHVTy/btsMxmPhUDf/EiZxm8BY1t99l0bkeDP3z0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qHVTy/btsMxmPhUDf/EiZxm8BY1t99l0bkeDP3z0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qHVTy/btsMxmPhUDf/EiZxm8BY1t99l0bkeDP3z0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqHVTy%2FbtsMxmPhUDf%2FEiZxm8BY1t99l0bkeDP3z0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_03.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_04.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oCuOb/btsMxGNq8ik/OkgSOk08huMUNXwIRxi6W0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oCuOb/btsMxGNq8ik/OkgSOk08huMUNXwIRxi6W0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oCuOb/btsMxGNq8ik/OkgSOk08huMUNXwIRxi6W0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoCuOb%2FbtsMxGNq8ik%2FOkgSOk08huMUNXwIRxi6W0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_04.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_05.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dy7zOh/btsMvx5BosN/3uz5OAdOasilDq7mUuTkmK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dy7zOh/btsMvx5BosN/3uz5OAdOasilDq7mUuTkmK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dy7zOh/btsMvx5BosN/3uz5OAdOasilDq7mUuTkmK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdy7zOh%2FbtsMvx5BosN%2F3uz5OAdOasilDq7mUuTkmK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_05.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_06.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xkmss/btsMx57ibHE/oviHNIgfO4P1vJ2LJ5d3tk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xkmss/btsMx57ibHE/oviHNIgfO4P1vJ2LJ5d3tk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xkmss/btsMx57ibHE/oviHNIgfO4P1vJ2LJ5d3tk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxkmss%2FbtsMx57ibHE%2FoviHNIgfO4P1vJ2LJ5d3tk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_06.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250225_210740201_07.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HnwYM/btsMvS9nCP0/8ErniKKOkrJn1NC2yh4Xek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HnwYM/btsMvS9nCP0/8ErniKKOkrJn1NC2yh4Xek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HnwYM/btsMvS9nCP0/8ErniKKOkrJn1NC2yh4Xek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHnwYM%2FbtsMvS9nCP0%2F8ErniKKOkrJn1NC2yh4Xek%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250225_210740201_07.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적으로 남은 작업을 생각해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 초성마켓 프로젝트에서는 Push는 사용하지 않을 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 로그아웃, 회원탈퇴, 자동 로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퀴즈 생성 페이지 디자인 및 기능 (공백 방지, 태그... 등등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 라우터 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캐시 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 홈 페이지 퀴즈 목록에 좋아요 버튼 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- QuizData에 sortNumber 추가해서 순서 정렬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Dialog 디자인 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로젝트 색상 변경 (현재는 임시 색상 조합입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미지 &amp;amp; 아이콘 적극 활용해서 적용 (프론트에서 이미지는 정말 중요합니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 작업량이 상당히 많아서 엄청 피곤하네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 퀴즈 생성 페이지 개발에 대해서 올리겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/568</guid>
      <comments>https://kwonputer.tistory.com/568#entry568comment</comments>
      <pubDate>Tue, 25 Feb 2025 21:44:54 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 명예의 전당 (유저, 퀴즈 랭킹) 기능 &amp;amp; 디자인 수정</title>
      <link>https://kwonputer.tistory.com/567</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740370357503&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6Xfkn/hyYjklaaB9/wDMSK0BORpI6LISUfrUBbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bGVZt3/hyYjuOSDsg/xmkaNntopeVRihsFGoLmK0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6Xfkn/hyYjklaaB9/wDMSK0BORpI6LISUfrUBbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bGVZt3/hyYjuOSDsg/xmkaNntopeVRihsFGoLmK0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 명예의 전당 페이지의 기능과 디자인을 수정하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인은 아래의 포스트에서 작성한대로 따라가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/564&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kwonputer.tistory.com/564&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740370400955&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[초성마켓] 디자인 탐색&quot; data-og-description=&quot;블로그는 비영리로 운영되고 있습니다.차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&amp;nbsp;https://github.com/KwonGeneral/chosungmarket.git&amp;nbsp;안녕하세요!초성마켓도 어느정도 기능이 완성이&quot; data-og-host=&quot;kwonputer.tistory.com&quot; data-og-source-url=&quot;https://kwonputer.tistory.com/564&quot; data-og-url=&quot;https://kwonputer.tistory.com/564&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b0JQVV/hyYjG2JlNN/lw7JwOPwb5sYM5rXQU3lqk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cfZYrH/hyYjrkiGul/1rjb2H6krlMvvr1aqfdZLK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bhDBaa/hyYjmpJ3vk/o4ICG9xIRBmMHkxblTKrE0/img.png?width=843&amp;amp;height=732&amp;amp;face=0_0_843_732&quot;&gt;&lt;a href=&quot;https://kwonputer.tistory.com/564&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kwonputer.tistory.com/564&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b0JQVV/hyYjG2JlNN/lw7JwOPwb5sYM5rXQU3lqk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cfZYrH/hyYjrkiGul/1rjb2H6krlMvvr1aqfdZLK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bhDBaa/hyYjmpJ3vk/o4ICG9xIRBmMHkxblTKrE0/img.png?width=843&amp;amp;height=732&amp;amp;face=0_0_843_732');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[초성마켓] 디자인 탐색&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;블로그는 비영리로 운영되고 있습니다.차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&amp;nbsp;https://github.com/KwonGeneral/chosungmarket.git&amp;nbsp;안녕하세요!초성마켓도 어느정도 기능이 완성이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kwonputer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랭킹은 기존의 hallOfFame을 '퀴즈 랭킹'으로 정의하고, 추가로 '유저 랭킹'을 추가해 볼 생각입니다. 기존 UserData의 point 필드를 활용할 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 'FirebaseUserDb'에 'point'필드를 정렬해서, 상위 n개만 가져오는 함수와 유저의 point 필드를 업데이트해주는 함수를 만들겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;1229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/olXdT/btsMu9ozs8y/IRFjEZuXFLBklG8VZImhn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/olXdT/btsMu9ozs8y/IRFjEZuXFLBklG8VZImhn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/olXdT/btsMu9ozs8y/IRFjEZuXFLBklG8VZImhn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FolXdT%2FbtsMu9ozs8y%2FIRFjEZuXFLBklG8VZImhn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1051&quot; height=&quot;1229&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;1229&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 domain 레이어의 'HallOfFameRepositoryImpl'와 'UserRepositoryImpl'에 상위 유저 목록 조회와 유저 포인트 업데이트 함수를 정의하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lxIv7/btsMtdzj6Ij/Cq8NMEGKybqZKwng2dzXE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lxIv7/btsMtdzj6Ij/Cq8NMEGKybqZKwng2dzXE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lxIv7/btsMtdzj6Ij/Cq8NMEGKybqZKwng2dzXE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlxIv7%2FbtsMtdzj6Ij%2FCq8NMEGKybqZKwng2dzXE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;354&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFy57M/btsMuLO3peV/sZSQQoaMZaKgcgvyenK2N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFy57M/btsMuLO3peV/sZSQQoaMZaKgcgvyenK2N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFy57M/btsMuLO3peV/sZSQQoaMZaKgcgvyenK2N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFy57M%2FbtsMuLO3peV%2FsZSQQoaMZaKgcgvyenK2N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;858&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, domain 레이어에 상위 랭킹 유저 목록을 조회하는 UseCase도 만들겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;823&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQAkMI/btsMs8LxfV7/SQAtnNj9ftePrw88t7htpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQAkMI/btsMs8LxfV7/SQAtnNj9ftePrw88t7htpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQAkMI/btsMs8LxfV7/SQAtnNj9ftePrw88t7htpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQAkMI%2FbtsMs8LxfV7%2FSQAtnNj9ftePrw88t7htpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;670&quot; height=&quot;823&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;823&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 data 레이어로 넘어와서, domain 레이어에서 정의한 impl의 구현체를 작성하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;1296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7ccW8/btsMva8SECC/cIKLMLjNPNW3M6xdR9x8f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7ccW8/btsMva8SECC/cIKLMLjNPNW3M6xdR9x8f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7ccW8/btsMva8SECC/cIKLMLjNPNW3M6xdR9x8f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7ccW8%2FbtsMva8SECC%2FcIKLMLjNPNW3M6xdR9x8f0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;1296&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;1296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DZUhV/btsMuexrg97/kFZwKxpnuyRgK06di5Zen0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DZUhV/btsMuexrg97/kFZwKxpnuyRgK06di5Zen0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DZUhV/btsMuexrg97/kFZwKxpnuyRgK06di5Zen0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDZUhV%2FbtsMuexrg97%2FkFZwKxpnuyRgK06di5Zen0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;1252&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고나서, 수정된 비즈니스 로직에 맞춰서 'ProcessQuizResultUseCase' 로직도 수정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPVt6z/btsMvyn9oMc/QV0KkF4OSLqFF08nlMHy10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPVt6z/btsMvyn9oMc/QV0KkF4OSLqFF08nlMHy10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPVt6z/btsMvyn9oMc/QV0KkF4OSLqFF08nlMHy10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPVt6z%2FbtsMvyn9oMc%2FQV0KkF4OSLqFF08nlMHy10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;966&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로, data 레이어와 domain 레이어의 DI도 수정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzZJX2/btsMvcr7MHc/FhxzkwtSOaNUC71a0v3w8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzZJX2/btsMvcr7MHc/FhxzkwtSOaNUC71a0v3w8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzZJX2/btsMvcr7MHc/FhxzkwtSOaNUC71a0v3w8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzZJX2%2FbtsMvcr7MHc%2FFhxzkwtSOaNUC71a0v3w8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;707&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5PghE/btsMsLXvMQ7/40lvv32vcD0UA5B01A5h4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5PghE/btsMsLXvMQ7/40lvv32vcD0UA5B01A5h4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5PghE/btsMsLXvMQ7/40lvv32vcD0UA5B01A5h4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5PghE%2FbtsMsLXvMQ7%2F40lvv32vcD0UA5B01A5h4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;682&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 presenter 레이어로 넘어와서, 디자인을 수정하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;1323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EEzpP/btsMuxDtTOz/LzaDvAR903Ejd02FXGkD40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EEzpP/btsMuxDtTOz/LzaDvAR903Ejd02FXGkD40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EEzpP/btsMuxDtTOz/LzaDvAR903Ejd02FXGkD40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEEzpP%2FbtsMuxDtTOz%2FLzaDvAR903Ejd02FXGkD40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;1323&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;1323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP4Wex/btsMvdR3i7L/48cbQijjDXhmvZDhrgmfRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP4Wex/btsMvdR3i7L/48cbQijjDXhmvZDhrgmfRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP4Wex/btsMvdR3i7L/48cbQijjDXhmvZDhrgmfRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP4Wex%2FbtsMvdR3i7L%2F48cbQijjDXhmvZDhrgmfRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;1318&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;1318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBDA1L/btsMvdYNSYj/BDrkDhuldcM4tuAU1qiKdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBDA1L/btsMvdYNSYj/BDrkDhuldcM4tuAU1qiKdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBDA1L/btsMvdYNSYj/BDrkDhuldcM4tuAU1qiKdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBDA1L%2FbtsMvdYNSYj%2FBDrkDhuldcM4tuAU1qiKdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;439&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250224_134105768.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLXPWO/btsMuKo6xyF/H27lNJb1Nmk8b3tPz8cPm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLXPWO/btsMuKo6xyF/H27lNJb1Nmk8b3tPz8cPm0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLXPWO/btsMuKo6xyF/H27lNJb1Nmk8b3tPz8cPm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLXPWO%2FbtsMuKo6xyF%2FH27lNJb1Nmk8b3tPz8cPm0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250224_134105768.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250224_134105768_01.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjtMC7/btsMtN09prg/pikOfzg8VXek0fI0YnFKPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjtMC7/btsMtN09prg/pikOfzg8VXek0fI0YnFKPk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjtMC7/btsMtN09prg/pikOfzg8VXek0fI0YnFKPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjtMC7%2FbtsMtN09prg%2FpikOfzg8VXek0fI0YnFKPk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250224_134105768_01.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 퀴즈 풀이에 대한 페이지 작업을 하겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/567</guid>
      <comments>https://kwonputer.tistory.com/567#entry567comment</comments>
      <pubDate>Mon, 24 Feb 2025 13:43:52 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 홈 페이지 디자인 변경</title>
      <link>https://kwonputer.tistory.com/566</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740031967195&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0I7qg/hyYfGCIUhL/D5rxczKcAJJNhHXzS51Ouk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bMG9fR/hyYjs3Ue5r/nJkiET1RuiAZDKCOaBdBDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0I7qg/hyYfGCIUhL/D5rxczKcAJJNhHXzS51Ouk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bMG9fR/hyYjs3Ue5r/nJkiET1RuiAZDKCOaBdBDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정사항으로 태그와 문제 풀이 횟수에 대한 usecase 등등 추가되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp28vm/btsMoGu1zz5/iY74wIhBYzRK3Qki95KpO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp28vm/btsMoGu1zz5/iY74wIhBYzRK3Qki95KpO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp28vm/btsMoGu1zz5/iY74wIhBYzRK3Qki95KpO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp28vm%2FbtsMoGu1zz5%2FiY74wIhBYzRK3Qki95KpO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;374&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crNehp/btsMoO0MZe8/Onj3K5ABjen7aX69sMkXZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crNehp/btsMoO0MZe8/Onj3K5ABjen7aX69sMkXZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crNehp/btsMoO0MZe8/Onj3K5ABjen7aX69sMkXZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrNehp%2FbtsMoO0MZe8%2FOnj3K5ABjen7aX69sMkXZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;372&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 정렬와 태그를 위한 types를 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIUixA/btsMpnakF96/Yk2BOvE0ua9Bcuk1TFjbMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIUixA/btsMpnakF96/Yk2BOvE0ua9Bcuk1TFjbMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIUixA/btsMpnakF96/Yk2BOvE0ua9Bcuk1TFjbMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIUixA%2FbtsMpnakF96%2FYk2BOvE0ua9Bcuk1TFjbMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;777&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc5WO2/btsMoIGh1nB/TUDvTdE8C1voFBFb9Lrj70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc5WO2/btsMoIGh1nB/TUDvTdE8C1voFBFb9Lrj70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc5WO2/btsMoIGh1nB/TUDvTdE8C1voFBFb9Lrj70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc5WO2%2FbtsMoIGh1nB%2FTUDvTdE8C1voFBFb9Lrj70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;802&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tcdBU/btsMompSPnZ/AvY88k4jT0hqwDfl50vPtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tcdBU/btsMompSPnZ/AvY88k4jT0hqwDfl50vPtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tcdBU/btsMompSPnZ/AvY88k4jT0hqwDfl50vPtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtcdBU%2FbtsMompSPnZ%2FAvY88k4jT0hqwDfl50vPtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;764&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cne7xX/btsMolkdUAj/5oJTKRXuZIgDuBJd4kuxt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cne7xX/btsMolkdUAj/5oJTKRXuZIgDuBJd4kuxt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cne7xX/btsMolkdUAj/5oJTKRXuZIgDuBJd4kuxt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcne7xX%2FbtsMolkdUAj%2F5oJTKRXuZIgDuBJd4kuxt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;772&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D2DKa/btsMqcznuk7/nBY6kbKHKdUt0hFliIXoK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D2DKa/btsMqcznuk7/nBY6kbKHKdUt0hFliIXoK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D2DKa/btsMqcznuk7/nBY6kbKHKdUt0hFliIXoK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD2DKa%2FbtsMqcznuk7%2FnBY6kbKHKdUt0hFliIXoK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;560&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3HvVA/btsMqZlMaYV/xlo5zGBNzPAWglZxCPmRFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3HvVA/btsMqZlMaYV/xlo5zGBNzPAWglZxCPmRFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3HvVA/btsMqZlMaYV/xlo5zGBNzPAWglZxCPmRFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3HvVA%2FbtsMqZlMaYV%2Fxlo5zGBNzPAWglZxCPmRFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1067&quot; height=&quot;785&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1279&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgGsVN/btsMokS8mAx/MExWWLP4izuOFpERdN7o6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgGsVN/btsMokS8mAx/MExWWLP4izuOFpERdN7o6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgGsVN/btsMokS8mAx/MExWWLP4izuOFpERdN7o6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgGsVN%2FbtsMokS8mAx%2FMExWWLP4izuOFpERdN7o6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1279&quot; height=&quot;774&quot; data-origin-width=&quot;1279&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc0EH3/btsMqzOCCwH/PJMt0FNeQShATlIF9CvtPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc0EH3/btsMqzOCCwH/PJMt0FNeQShATlIF9CvtPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc0EH3/btsMqzOCCwH/PJMt0FNeQShATlIF9CvtPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc0EH3%2FbtsMqzOCCwH%2FPJMt0FNeQShATlIF9CvtPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;376&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈를 푼 횟수를 가져오기 위해, 코드를 수정 &amp;amp; 추가 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;1321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKbL4/btsMoNOgPVS/Z0Hh5tbzk9xnhvO856uAx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKbL4/btsMoNOgPVS/Z0Hh5tbzk9xnhvO856uAx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKbL4/btsMoNOgPVS/Z0Hh5tbzk9xnhvO856uAx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKbL4%2FbtsMoNOgPVS%2FZ0Hh5tbzk9xnhvO856uAx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1121&quot; height=&quot;1321&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;1321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 올렸던, 디자인 탐색 포스트를 참고해서 얼추 비슷하게 만들었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/omdvu/btsMo5Ou6Ks/pe9psB9jhWIfju6ZYdGVUK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/omdvu/btsMo5Ou6Ks/pe9psB9jhWIfju6ZYdGVUK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/omdvu/btsMo5Ou6Ks/pe9psB9jhWIfju6ZYdGVUK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fomdvu%2FbtsMo5Ou6Ks%2Fpe9psB9jhWIfju6ZYdGVUK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot; data-filename=&quot;1.jpg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/566</guid>
      <comments>https://kwonputer.tistory.com/566#entry566comment</comments>
      <pubDate>Thu, 20 Feb 2025 21:11:18 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - Firebase DB 구조 변경</title>
      <link>https://kwonputer.tistory.com/565</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740031960475&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0I7qg/hyYfGCIUhL/D5rxczKcAJJNhHXzS51Ouk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bMG9fR/hyYjs3Ue5r/nJkiET1RuiAZDKCOaBdBDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0I7qg/hyYfGCIUhL/D5rxczKcAJJNhHXzS51Ouk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bMG9fR/hyYjs3Ue5r/nJkiET1RuiAZDKCOaBdBDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트의 Firebase DB 구조를 변경하려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJig7d/btsMpAG1Z1w/lksRKR9wcRTzpwKTT9b1U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJig7d/btsMpAG1Z1w/lksRKR9wcRTzpwKTT9b1U1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJig7d/btsMpAG1Z1w/lksRKR9wcRTzpwKTT9b1U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJig7d%2FbtsMpAG1Z1w%2FlksRKR9wcRTzpwKTT9b1U1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1256&quot; height=&quot;340&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;quizGroupList와 quizResultList를 메인 컬렉션으로 빼고, 유저가 가지고 있는건 ids로 설정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ids는 user 필드에 넣을 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서는 프로젝트의 Firebase 관련 코드들을 모두 건드려야해서 벌써 귀찮네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 필요한 작업이니 집중해서 빠르게 끝내겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 모델 클래스부터 수정하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIk6Cg/btsMoZHsRtQ/20sQfctxSkAgtpJrNgSk9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIk6Cg/btsMoZHsRtQ/20sQfctxSkAgtpJrNgSk9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIk6Cg/btsMoZHsRtQ/20sQfctxSkAgtpJrNgSk9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIk6Cg%2FbtsMoZHsRtQ%2F20sQfctxSkAgtpJrNgSk9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;740&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v1n5v/btsMn1lN3wt/nOXopDkblo10cfKgcTNiQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v1n5v/btsMn1lN3wt/nOXopDkblo10cfKgcTNiQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v1n5v/btsMn1lN3wt/nOXopDkblo10cfKgcTNiQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv1n5v%2FbtsMn1lN3wt%2FnOXopDkblo10cfKgcTNiQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;777&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로, Firebase DB 코드들을 수정해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boDzpX/btsMqCKXR47/4g20mPirQaCZMObrrkXAk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boDzpX/btsMqCKXR47/4g20mPirQaCZMObrrkXAk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boDzpX/btsMqCKXR47/4g20mPirQaCZMObrrkXAk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboDzpX%2FbtsMqCKXR47%2F4g20mPirQaCZMObrrkXAk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;856&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;1092&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMh5iR/btsMqCxrcjX/mK4STMCONzvIusFxRCB2i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMh5iR/btsMqCxrcjX/mK4STMCONzvIusFxRCB2i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMh5iR/btsMqCxrcjX/mK4STMCONzvIusFxRCB2i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMh5iR%2FbtsMqCxrcjX%2FmK4STMCONzvIusFxRCB2i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;836&quot; height=&quot;1092&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;1092&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuJQfl/btsMp9WH5Bp/0g9HlAqlof6Fqxb9H5ps11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuJQfl/btsMp9WH5Bp/0g9HlAqlof6Fqxb9H5ps11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuJQfl/btsMp9WH5Bp/0g9HlAqlof6Fqxb9H5ps11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuJQfl%2FbtsMp9WH5Bp%2F0g9HlAqlof6Fqxb9H5ps11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1036&quot; height=&quot;1143&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 Mapper 관련 코드를 수정해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;1301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2cD28/btsMoYBSGc0/XTyDOSIeBl6okyGiFILMh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2cD28/btsMoYBSGc0/XTyDOSIeBl6okyGiFILMh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2cD28/btsMoYBSGc0/XTyDOSIeBl6okyGiFILMh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2cD28%2FbtsMoYBSGc0%2FXTyDOSIeBl6okyGiFILMh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;1301&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;1301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDcws1/btsMpmWtTkD/aTGArUURHKwZeiNS7lpsQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDcws1/btsMpmWtTkD/aTGArUURHKwZeiNS7lpsQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDcws1/btsMpmWtTkD/aTGArUURHKwZeiNS7lpsQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDcws1%2FbtsMpmWtTkD%2FaTGArUURHKwZeiNS7lpsQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;889&quot; height=&quot;1000&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;domain 레이어의 repository impl도 수정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwmCfo/btsMoERbbZU/rQsKz3A7bmbemMwBLVm89k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwmCfo/btsMoERbbZU/rQsKz3A7bmbemMwBLVm89k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwmCfo/btsMoERbbZU/rQsKz3A7bmbemMwBLVm89k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwmCfo%2FbtsMoERbbZU%2FrQsKz3A7bmbemMwBLVm89k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;824&quot; height=&quot;798&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bILLl5/btsMoxLqxZb/MCf9X13fbKlRm91zDKwkX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bILLl5/btsMoxLqxZb/MCf9X13fbKlRm91zDKwkX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bILLl5/btsMoxLqxZb/MCf9X13fbKlRm91zDKwkX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbILLl5%2FbtsMoxLqxZb%2FMCf9X13fbKlRm91zDKwkX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;884&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data 레이어의 repository도 수정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;1245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDcPI4/btsMqxJTLZ6/vNm8n91XKrgN1SGHp7K2n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDcPI4/btsMqxJTLZ6/vNm8n91XKrgN1SGHp7K2n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDcPI4/btsMqxJTLZ6/vNm8n91XKrgN1SGHp7K2n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDcPI4%2FbtsMqxJTLZ6%2FvNm8n91XKrgN1SGHp7K2n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;879&quot; height=&quot;1245&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;1245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;1294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DQIg8/btsMpCZlBlM/UnOvXrpX8KoGykAmm5yhGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DQIg8/btsMpCZlBlM/UnOvXrpX8KoGykAmm5yhGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DQIg8/btsMpCZlBlM/UnOvXrpX8KoGykAmm5yhGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDQIg8%2FbtsMpCZlBlM%2FUnOvXrpX8KoGykAmm5yhGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;869&quot; height=&quot;1294&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;1294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;domain 레이어의 usecase도 수정해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;1283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FCe7f/btsMo3QCBaF/kLSkPGAdkK3I2YhpUXADL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FCe7f/btsMo3QCBaF/kLSkPGAdkK3I2YhpUXADL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FCe7f/btsMo3QCBaF/kLSkPGAdkK3I2YhpUXADL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFCe7f%2FbtsMo3QCBaF%2FkLSkPGAdkK3I2YhpUXADL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;1283&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;1283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blho4F/btsMpm3oIlT/06RlWj8PbMmc8M7QohamQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blho4F/btsMpm3oIlT/06RlWj8PbMmc8M7QohamQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blho4F/btsMpm3oIlT/06RlWj8PbMmc8M7QohamQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblho4F%2FbtsMpm3oIlT%2F06RlWj8PbMmc8M7QohamQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;848&quot; height=&quot;900&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 확인을 해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZmZ4w/btsMokSXlVg/rYIvBeKtThO7F44GVBXm1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZmZ4w/btsMokSXlVg/rYIvBeKtThO7F44GVBXm1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZmZ4w/btsMokSXlVg/rYIvBeKtThO7F44GVBXm1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZmZ4w%2FbtsMokSXlVg%2FrYIvBeKtThO7F44GVBXm1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;319&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Slbhv/btsMogiWkQl/7dc0XcWIgzdGglVIa8wkDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Slbhv/btsMogiWkQl/7dc0XcWIgzdGglVIa8wkDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Slbhv/btsMogiWkQl/7dc0XcWIgzdGglVIa8wkDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSlbhv%2FbtsMogiWkQl%2F7dc0XcWIgzdGglVIa8wkDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;901&quot; height=&quot;310&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mPlwN/btsMqPKbI4l/BagDFXaE1ZKgvkBgKCeOv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mPlwN/btsMqPKbI4l/BagDFXaE1ZKgvkBgKCeOv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mPlwN/btsMqPKbI4l/BagDFXaE1ZKgvkBgKCeOv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmPlwN%2FbtsMqPKbI4l%2FBagDFXaE1ZKgvkBgKCeOv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1004&quot; height=&quot;604&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 동작하네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 디자인 수정으로 넘어가면 될 것 같습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/565</guid>
      <comments>https://kwonputer.tistory.com/565#entry565comment</comments>
      <pubDate>Thu, 20 Feb 2025 14:08:35 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 디자인 탐색</title>
      <link>https://kwonputer.tistory.com/564</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초성마켓도 어느정도 기능이 완성이 되었는데요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 디자인에 신경쓸 때가 왔습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 오늘은 참고할만한 디자인을 알아보려고하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀터레스트를 통해서 찾아보도록 하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저작권 문제가 있을수도 있으니, 사진은 일부분만 올리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/teuRw/btsMqkKiXJt/NIsmDmey5vVeHRCYScgKBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/teuRw/btsMqkKiXJt/NIsmDmey5vVeHRCYScgKBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/teuRw/btsMqkKiXJt/NIsmDmey5vVeHRCYScgKBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FteuRw%2FbtsMqkKiXJt%2FNIsmDmey5vVeHRCYScgKBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;843&quot; height=&quot;732&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초성퀴즈를 풀 때는 위 처럼 단순하게 가는게 좋아 보이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙에 초성을 표시하고, 아래에 힌트를 표시.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최하단에 정답 입력창을 만들도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GXaAG/btsMpiNeR59/DE0MQ8XykJvc10CPNBDrTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GXaAG/btsMpiNeR59/DE0MQ8XykJvc10CPNBDrTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GXaAG/btsMpiNeR59/DE0MQ8XykJvc10CPNBDrTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGXaAG%2FbtsMpiNeR59%2FDE0MQ8XykJvc10CPNBDrTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;445&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인별로 포인트 랭킹도 만들면 좋겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명예의 전당도 위 처럼 만들면 깔끔할 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6BVyp/btsMpKigfct/Kxu4PXV071NQRKZJElHUY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6BVyp/btsMpKigfct/Kxu4PXV071NQRKZJElHUY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6BVyp/btsMpKigfct/Kxu4PXV071NQRKZJElHUY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6BVyp%2FbtsMpKigfct%2FKxu4PXV071NQRKZJElHUY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;323&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 페이지는 위 처럼 해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가작업이 필요하겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태그작업과 대표이미지도 만들어야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표이미지는 고민이네요.. 프로젝트에 최대한 돈이 안들었으면 좋겠어서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 이미지 제네레이션을 활용해서 몇 개 이미지를 뽑아서 설정하도록 해야겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbhvIe/btsMnRKc3OZ/rtyXENC5szHIOfIKlrb531/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbhvIe/btsMnRKc3OZ/rtyXENC5szHIOfIKlrb531/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbhvIe/btsMnRKc3OZ/rtyXENC5szHIOfIKlrb531/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbhvIe%2FbtsMnRKc3OZ%2FrtyXENC5szHIOfIKlrb531%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;247&quot; height=&quot;696&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로필 이미지는 이런 방식으로 선택하면 좋아 보이네요. 카메라와 갤러리 버튼만 제거하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;829&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEhGhm/btsMoQ4QsOP/yexU02KcrxyOXrEsdjQ62k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEhGhm/btsMoQ4QsOP/yexU02KcrxyOXrEsdjQ62k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEhGhm/btsMoQ4QsOP/yexU02KcrxyOXrEsdjQ62k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEhGhm%2FbtsMoQ4QsOP%2FyexU02KcrxyOXrEsdjQ62k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;829&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;829&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 생성에 대한 참고 자료를 찾기가 힘드네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 위에 방식처럼 깔끔하게 하도록 해야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 제목&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 태그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대표이미지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퀴즈목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 퀴즈 상세 페이지를 삭제하고, 퀴즈를 클릭하면 제목&amp;amp;내용을 보여주고 바로 풀 수 있는 다이얼로그 방식으로 가려고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDuUdF/btsMpKpapf5/95l0vL4emSJ2JopkVTDHMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDuUdF/btsMpKpapf5/95l0vL4emSJ2JopkVTDHMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDuUdF/btsMpKpapf5/95l0vL4emSJ2JopkVTDHMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDuUdF%2FbtsMpKpapf5%2F95l0vL4emSJ2JopkVTDHMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;712&quot; height=&quot;488&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 관련해서 Firebase의 Storage를 사용하려고 했는데, 역시 유료네요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 업그레이드를 한다고해서 돈이 무조건 빠져나가는건 아니지만, 그래도 패스하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지는 미리 빌드해서 나가는걸로 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;DB 관려해서도 수정 사항이 생겼네요.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;전제조건으로 퀴즈의 수정이 불가능하도록 설정하려고합니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;태그는 1개만 가능하도록 하겠습니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;user에 물려있던 quizGroupList를 빼서 컬렉션으로 만들고, user가 가진건 quizGroupList의 Ids로 하겠습니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;quizGroupList에도 tags를 만들어서, 대표 tags로 설정하고, quizList가 가진 tags는 서브 tags로 설정해서 우선은 대표 tags를 따라가도록 만들어야겠습니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;quizGroupResults도 현재는 userList&amp;gt;{userId}&amp;gt;quizGroupResults로 구성했는데, 이 또한 밖으로 빼내서, 컬렉션으로 만들고, 유저는 Ids만 갖도록 설정하겠습니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/564</guid>
      <comments>https://kwonputer.tistory.com/564#entry564comment</comments>
      <pubDate>Thu, 20 Feb 2025 13:17:55 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 명예의 전당 개발</title>
      <link>https://kwonputer.tistory.com/563</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739927201902&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b1eIrw/hyYjgWxdac/5onpgyqJNFeMmASa1siM2K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/e4bTp/hyYfVT4Gt7/6HAHbmu2U0LKdU0k0QRjIK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b1eIrw/hyYjgWxdac/5onpgyqJNFeMmASa1siM2K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/e4bTp/hyYfVT4Gt7/6HAHbmu2U0LKdU0k0QRjIK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 초성마켓 프로젝트의 명예의 전당 기능과 페이지를 개발하려고합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 바텀네비에 명예의 전당을 추가시키고, isShow 하는 부분에도 추가시켜줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byza04/btsMm6GGRFO/MFsldydw8sWymZmWE4iry0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byza04/btsMm6GGRFO/MFsldydw8sWymZmWE4iry0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byza04/btsMm6GGRFO/MFsldydw8sWymZmWE4iry0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyza04%2FbtsMm6GGRFO%2FMFsldydw8sWymZmWE4iry0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;784&quot; height=&quot;572&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E4EB1/btsMno8aDoB/2KRaUlBP3ALPmLX5uF2kX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E4EB1/btsMno8aDoB/2KRaUlBP3ALPmLX5uF2kX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E4EB1/btsMno8aDoB/2KRaUlBP3ALPmLX5uF2kX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE4EB1%2FbtsMno8aDoB%2F2KRaUlBP3ALPmLX5uF2kX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;468&quot; height=&quot;394&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음으로는, 명예의 전당 기능 관련해서 코드를 작성해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 백엔드 없이 firebase만 사용하기 때문에, 배치를 돌리기가 애매한 점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 항상 전체 쿼리 검색으로 하기에는 코스트 낭비가 심할 것 같구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래처럼 진행해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 명예의 전당은 1~100위까지 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 유저가 명예의 전당 페이지에 진입 시, firestore의 명예의 전당 db에 현재 날짜가 없다면 쿼리문 실행해서 오늘 날짜의 명예의 전당 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 명예의 전당은 quizGroupList를 사용하며, 최소 추천수가 n갯수 이상인 데이터를 대상으로 쿼리 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, Firestore에서 추천수가 n개 이상인 quizGroupList를 가져오는 함수를 만들어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwih5k/btsMn1q0MrA/dOT8vkymo5GoFvwmXQrRY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwih5k/btsMn1q0MrA/dOT8vkymo5GoFvwmXQrRY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwih5k/btsMn1q0MrA/dOT8vkymo5GoFvwmXQrRY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwih5k%2FbtsMn1q0MrA%2FdOT8vkymo5GoFvwmXQrRY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;887&quot; height=&quot;772&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로는 명예의 전당 Db 코드도 수정하려고 하는데요, 위에 필요한 기능에 적어놨듯이 '현재 날짜'가 필요합니다. 하지만, LocalDate 등을 사용하게 되면, 오늘 날짜를 가져올 수는 있지만 문제가 있습니다. 사용자가 직접 휴대폰의 날짜를 수정한 경우, 해당 날짜를 그대로 가지고 오는 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 firebase에서 지원하는 serverTimestamp를 사용해서 로직을 개발했습니다~!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YqJjr/btsMnR9P6TT/bw7mkkpZ10kQ5gbiJRkSC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YqJjr/btsMnR9P6TT/bw7mkkpZ10kQ5gbiJRkSC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YqJjr/btsMnR9P6TT/bw7mkkpZ10kQ5gbiJRkSC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYqJjr%2FbtsMnR9P6TT%2Fbw7mkkpZ10kQ5gbiJRkSC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;887&quot; height=&quot;1236&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, data 레이어의 repository도 수정해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해 최소 추천 수는 0개로 잡았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boRmJ1/btsMnZGEv9p/i93KViMCf7EbgszXKH61Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boRmJ1/btsMnZGEv9p/i93KViMCf7EbgszXKH61Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boRmJ1/btsMnZGEv9p/i93KViMCf7EbgszXKH61Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboRmJ1%2FbtsMnZGEv9p%2Fi93KViMCf7EbgszXKH61Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1033&quot; height=&quot;1234&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;1234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 명예의 전당 페이지를 쭉쭉 개발해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;1336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Mmbz/btsMn7xPN3H/OMB8yGD5nCfXnagFAbYUv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Mmbz/btsMn7xPN3H/OMB8yGD5nCfXnagFAbYUv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Mmbz/btsMn7xPN3H/OMB8yGD5nCfXnagFAbYUv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Mmbz%2FbtsMn7xPN3H%2FOMB8yGD5nCfXnagFAbYUv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;1336&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;1336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 만들고보니 빼먹은게 있었네요. 아이템 클릭 시에, 퀴즈를 풀 수 있도록 처리합시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWPuQL/btsMmo2jcf3/WNokBUyDXiaBWyPlK9W3R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWPuQL/btsMmo2jcf3/WNokBUyDXiaBWyPlK9W3R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWPuQL/btsMmo2jcf3/WNokBUyDXiaBWyPlK9W3R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWPuQL%2FbtsMmo2jcf3%2FWNokBUyDXiaBWyPlK9W3R1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;284&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot_20250219_114739.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kH4SR/btsMnsvYgkj/h0HSyQml5RifKtsp1DfgFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kH4SR/btsMnsvYgkj/h0HSyQml5RifKtsp1DfgFK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kH4SR/btsMnsvYgkj/h0HSyQml5RifKtsp1DfgFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkH4SR%2FbtsMnsvYgkj%2Fh0HSyQml5RifKtsp1DfgFK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2316&quot; data-filename=&quot;Screenshot_20250219_114739.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot_20250219_114743.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2q5OS/btsMnBGdRb7/yagbfd1Ngm4Iij5HBg5fCk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2q5OS/btsMnBGdRb7/yagbfd1Ngm4Iij5HBg5fCk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2q5OS/btsMnBGdRb7/yagbfd1Ngm4Iij5HBg5fCk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2q5OS%2FbtsMnBGdRb7%2Fyagbfd1Ngm4Iij5HBg5fCk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2316&quot; data-filename=&quot;Screenshot_20250219_114743.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스트는 여기서 마치겠습니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 메인 기능은 완성했으니, 디테일을 잡아야겠네요!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/563</guid>
      <comments>https://kwonputer.tistory.com/563#entry563comment</comments>
      <pubDate>Wed, 19 Feb 2025 11:52:35 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (10) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/562</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNfY9W/btsMmNGRAu9/ujpoOolK6TsgVfaoSzcq41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNfY9W/btsMmNGRAu9/ujpoOolK6TsgVfaoSzcq41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNfY9W/btsMmNGRAu9/ujpoOolK6TsgVfaoSzcq41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNfY9W%2FbtsMmNGRAu9%2FujpoOolK6TsgVfaoSzcq41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;756&quot; height=&quot;372&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4rFCy/btsMnBsbLS8/5kN26eSKMosSBVFyykZe1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4rFCy/btsMnBsbLS8/5kN26eSKMosSBVFyykZe1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4rFCy/btsMnBsbLS8/5kN26eSKMosSBVFyykZe1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4rFCy%2FbtsMnBsbLS8%2F5kN26eSKMosSBVFyykZe1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;686&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 1분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 너무 쉬운 문제가 나왔네요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brrN48/btsMnPjpCmN/cKAUQ2Yc2nL9SzWKQnNkl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brrN48/btsMnPjpCmN/cKAUQ2Yc2nL9SzWKQnNkl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brrN48/btsMnPjpCmN/cKAUQ2Yc2nL9SzWKQnNkl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrrN48%2FbtsMnPjpCmN%2FcKAUQ2Yc2nL9SzWKQnNkl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;1104&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD68Ip/btsMn1cTR3D/YwhWHJX6JvEeFaBHEEr5EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD68Ip/btsMn1cTR3D/YwhWHJX6JvEeFaBHEEr5EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD68Ip/btsMn1cTR3D/YwhWHJX6JvEeFaBHEEr5EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD68Ip%2FbtsMn1cTR3D%2FYwhWHJX6JvEeFaBHEEr5EK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1225&quot; height=&quot;1040&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b92C7E/btsMlOUcVn2/08F2Rk51HYtMjLs4JW3lTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b92C7E/btsMlOUcVn2/08F2Rk51HYtMjLs4JW3lTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b92C7E/btsMlOUcVn2/08F2Rk51HYtMjLs4JW3lTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb92C7E%2FbtsMlOUcVn2%2F08F2Rk51HYtMjLs4JW3lTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;599&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J3lf3/btsMnrwBBJg/fTj7nrE411MUvjnO5l8WJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J3lf3/btsMnrwBBJg/fTj7nrE411MUvjnO5l8WJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J3lf3/btsMnrwBBJg/fTj7nrE411MUvjnO5l8WJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ3lf3%2FbtsMnrwBBJg%2FfTj7nrE411MUvjnO5l8WJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1016&quot; height=&quot;905&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;891&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ7iaB/btsMm0TBjbZ/BpH5RKadqu7jHvMzJmNqIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ7iaB/btsMm0TBjbZ/BpH5RKadqu7jHvMzJmNqIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ7iaB/btsMm0TBjbZ/BpH5RKadqu7jHvMzJmNqIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ7iaB%2FbtsMm0TBjbZ%2FBpH5RKadqu7jHvMzJmNqIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1558&quot; height=&quot;891&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;891&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 21분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이번 난이도는 쉬운 편이었네요. 이렇게 쉬운 난이도가 나온 건 처음이네요. 2번 문제 때문에, 항상 시간을 1분 남겨두고 제출했었는데, 이번 문제는 아무래도 난이도 조절에 실패한 느낌이네요!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/562</guid>
      <comments>https://kwonputer.tistory.com/562#entry562comment</comments>
      <pubDate>Tue, 18 Feb 2025 16:39:06 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (9) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/561</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;1142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mkv5l/btsMl7FM1Z6/wQ8CCaUGV31Sd13VDLqkSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mkv5l/btsMl7FM1Z6/wQ8CCaUGV31Sd13VDLqkSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mkv5l/btsMl7FM1Z6/wQ8CCaUGV31Sd13VDLqkSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmkv5l%2FbtsMl7FM1Z6%2FwQ8CCaUGV31Sd13VDLqkSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1487&quot; height=&quot;1142&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;1142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A3QiY/btsMnoT1qpG/kWpGMMuOtr3Xm5ZQkR0On0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A3QiY/btsMnoT1qpG/kWpGMMuOtr3Xm5ZQkR0On0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A3QiY/btsMnoT1qpG/kWpGMMuOtr3Xm5ZQkR0On0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA3QiY%2FbtsMnoT1qpG%2FkWpGMMuOtr3Xm5ZQkR0On0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1488&quot; height=&quot;847&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;847&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;1127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjyvZx/btsMlHgsqBz/6IKskoqj0F6Vp9iEerKi8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjyvZx/btsMlHgsqBz/6IKskoqj0F6Vp9iEerKi8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjyvZx/btsMlHgsqBz/6IKskoqj0F6Vp9iEerKi8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjyvZx%2FbtsMlHgsqBz%2F6IKskoqj0F6Vp9iEerKi8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;1127&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;1127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 16분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생각보다 여기서 시간을 많이 잡아먹었네요ㅠㅠ&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2180&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/breSmj/btsMnyCclem/ub0qI2xsukylsb2aR6dCp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/breSmj/btsMnyCclem/ub0qI2xsukylsb2aR6dCp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/breSmj/btsMnyCclem/ub0qI2xsukylsb2aR6dCp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbreSmj%2FbtsMnyCclem%2Fub0qI2xsukylsb2aR6dCp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2180&quot; height=&quot;934&quot; data-origin-width=&quot;2180&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eo7oqb/btsMmfwYC9x/uKNqBH0k2kXjAGL6kT09jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eo7oqb/btsMmfwYC9x/uKNqBH0k2kXjAGL6kT09jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eo7oqb/btsMmfwYC9x/uKNqBH0k2kXjAGL6kT09jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feo7oqb%2FbtsMmfwYC9x%2FuKNqBH0k2kXjAGL6kT09jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1263&quot; height=&quot;433&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;997&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfMtpt/btsMmYuCWlj/N1IGCKsh3QXGTHLNlQwyN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfMtpt/btsMmYuCWlj/N1IGCKsh3QXGTHLNlQwyN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfMtpt/btsMmYuCWlj/N1IGCKsh3QXGTHLNlQwyN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfMtpt%2FbtsMmYuCWlj%2FN1IGCKsh3QXGTHLNlQwyN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1223&quot; height=&quot;997&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;997&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfrAO2/btsMmsQquOE/lERdEXrTNwFv5Lp3x0jb0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfrAO2/btsMmsQquOE/lERdEXrTNwFv5Lp3x0jb0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfrAO2/btsMmsQquOE/lERdEXrTNwFv5Lp3x0jb0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfrAO2%2FbtsMmsQquOE%2FlERdEXrTNwFv5Lp3x0jb0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;933&quot; height=&quot;718&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;1191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgVTd/btsMlPSZojI/FISKCIdtXjPTsNvZ2PD8w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgVTd/btsMlPSZojI/FISKCIdtXjPTsNvZ2PD8w0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgVTd/btsMlPSZojI/FISKCIdtXjPTsNvZ2PD8w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgVTd%2FbtsMlPSZojI%2FFISKCIdtXjPTsNvZ2PD8w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1478&quot; height=&quot;1191&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;1191&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 26분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 계속 시간이 촉박하게 끝나네요. 이번에는 오프닝 체크를 2번 해줘야하는데, 1번만 해줘서 계속 결과값이 하나씩 빗나가더라고요. 타이밍 문제로 생각해서 접근하다가 마지막에 실수를 깨닫고 바로 수정하고 채점했습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/561</guid>
      <comments>https://kwonputer.tistory.com/561#entry561comment</comments>
      <pubDate>Tue, 18 Feb 2025 15:30:01 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (8) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/560</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6WVMC/btsMm0sfhNA/Emn7P6dKrWR2jzzLyFrWZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6WVMC/btsMm0sfhNA/Emn7P6dKrWR2jzzLyFrWZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6WVMC/btsMm0sfhNA/Emn7P6dKrWR2jzzLyFrWZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6WVMC%2FbtsMm0sfhNA%2FEmn7P6dKrWR2jzzLyFrWZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;969&quot; height=&quot;714&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvJAPU/btsMlpGWMeg/MftnDIWCh8xDknEGIU2DnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvJAPU/btsMlpGWMeg/MftnDIWCh8xDknEGIU2DnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvJAPU/btsMlpGWMeg/MftnDIWCh8xDknEGIU2DnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvJAPU%2FbtsMlpGWMeg%2FMftnDIWCh8xDknEGIU2DnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;794&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 6분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 인덱스에 같은 문자인 경우에 대한 처리를 생각하다가 시간을 좀 잡아먹었네요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;1067&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5lt9j/btsMm24Gei2/sXJMv5XuiivrpH3WYtuLU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5lt9j/btsMm24Gei2/sXJMv5XuiivrpH3WYtuLU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5lt9j/btsMm24Gei2/sXJMv5XuiivrpH3WYtuLU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5lt9j%2FbtsMm24Gei2%2FsXJMv5XuiivrpH3WYtuLU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1868&quot; height=&quot;1067&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;1067&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KVazO/btsMnyhNLwQ/PuBB2BG9XK9LLI8cbcoBf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KVazO/btsMnyhNLwQ/PuBB2BG9XK9LLI8cbcoBf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KVazO/btsMnyhNLwQ/PuBB2BG9XK9LLI8cbcoBf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKVazO%2FbtsMnyhNLwQ%2FPuBB2BG9XK9LLI8cbcoBf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1915&quot; height=&quot;1022&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUgeRz/btsMmbnN71J/0UjLBIvp02OvzYGgViTUO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUgeRz/btsMmbnN71J/0UjLBIvp02OvzYGgViTUO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUgeRz/btsMmbnN71J/0UjLBIvp02OvzYGgViTUO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUgeRz%2FbtsMmbnN71J%2F0UjLBIvp02OvzYGgViTUO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;838&quot; height=&quot;539&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;1006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clBQ8o/btsMlG2NDFG/okXILzKyYNijTV68DaiJkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clBQ8o/btsMlG2NDFG/okXILzKyYNijTV68DaiJkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clBQ8o/btsMlG2NDFG/okXILzKyYNijTV68DaiJkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclBQ8o%2FbtsMlG2NDFG%2FokXILzKyYNijTV68DaiJkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1483&quot; height=&quot;1006&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;1006&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lOwSk/btsMnOECv52/k0DMZD8ouWhHWzWraR8JIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lOwSk/btsMnOECv52/k0DMZD8ouWhHWzWraR8JIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lOwSk/btsMnOECv52/k0DMZD8ouWhHWzWraR8JIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlOwSk%2FbtsMnOECv52%2Fk0DMZD8ouWhHWzWraR8JIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;831&quot; height=&quot;577&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GIBg7/btsMlEjFvWH/8X8aQkAKCvvENm5FgIobrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIBg7/btsMlEjFvWH/8X8aQkAKCvvENm5FgIobrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIBg7/btsMlEjFvWH/8X8aQkAKCvvENm5FgIobrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIBg7%2FbtsMlEjFvWH%2F8X8aQkAKCvvENm5FgIobrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1543&quot; height=&quot;960&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 33분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예전에 풀었던 문제가 나와서 여유를 가지고 다른 방법으로 풀어봤는데, '시간 초과'가 뜨더라구요.. 그래서 부랴부랴 코드를 수정하느라 이번에도 아슬아슬하게 풀었네요ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 2번 정도 Lv.1을 풀어보고 넘어가려고 합니다~&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/560</guid>
      <comments>https://kwonputer.tistory.com/560#entry560comment</comments>
      <pubDate>Tue, 18 Feb 2025 14:40:05 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (7) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/559</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccg4Nl/btsMlCMOKQ1/HBSJiAg0ls2BxqjFmsT2yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccg4Nl/btsMlCMOKQ1/HBSJiAg0ls2BxqjFmsT2yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccg4Nl/btsMlCMOKQ1/HBSJiAg0ls2BxqjFmsT2yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccg4Nl%2FbtsMlCMOKQ1%2FHBSJiAg0ls2BxqjFmsT2yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;906&quot; height=&quot;783&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;1007&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs9v3E/btsMlo2eyzQ/lkrk0occBrjn09GMlwk3vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs9v3E/btsMlo2eyzQ/lkrk0occBrjn09GMlwk3vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs9v3E/btsMlo2eyzQ/lkrk0occBrjn09GMlwk3vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs9v3E%2FbtsMlo2eyzQ%2Flkrk0occBrjn09GMlwk3vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;729&quot; height=&quot;1007&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;1007&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 4분&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1797&quot; data-origin-height=&quot;1075&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEaFA1/btsMmLBUmJP/XLr6NT9hDC1i6MdWvXkpfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEaFA1/btsMmLBUmJP/XLr6NT9hDC1i6MdWvXkpfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEaFA1/btsMmLBUmJP/XLr6NT9hDC1i6MdWvXkpfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEaFA1%2FbtsMmLBUmJP%2FXLr6NT9hDC1i6MdWvXkpfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1797&quot; height=&quot;1075&quot; data-origin-width=&quot;1797&quot; data-origin-height=&quot;1075&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhM2C3/btsMlnoMm85/siKJwx1XUlPvahlUR7A6Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhM2C3/btsMlnoMm85/siKJwx1XUlPvahlUR7A6Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhM2C3/btsMlnoMm85/siKJwx1XUlPvahlUR7A6Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhM2C3%2FbtsMlnoMm85%2FsiKJwx1XUlPvahlUR7A6Tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1041&quot; height=&quot;647&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH0fFJ/btsMnNr72x3/cIfnlUbQ1vo0c0blt0htf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH0fFJ/btsMnNr72x3/cIfnlUbQ1vo0c0blt0htf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH0fFJ/btsMnNr72x3/cIfnlUbQ1vo0c0blt0htf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH0fFJ%2FbtsMnNr72x3%2FcIfnlUbQ1vo0c0blt0htf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;973&quot; height=&quot;744&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dukrme/btsMlPFbK6v/1jJNKKfo449ktNZ49OszQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dukrme/btsMlPFbK6v/1jJNKKfo449ktNZ49OszQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dukrme/btsMlPFbK6v/1jJNKKfo449ktNZ49OszQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdukrme%2FbtsMlPFbK6v%2F1jJNKKfo449ktNZ49OszQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1505&quot; height=&quot;737&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;737&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 11분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음.. 이번 문제는 난이도가 너무 쉽네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 코딩 테스트는 난이도 조절이 어려워 보이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 어렵거나 너무 쉽거나.. 둘 중 하나네요.&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/559</guid>
      <comments>https://kwonputer.tistory.com/559#entry559comment</comments>
      <pubDate>Tue, 18 Feb 2025 13:56:45 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (6) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/558</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Th6xS/btsMmOxVR1I/7DORjqYitygmyksiTy0q70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Th6xS/btsMmOxVR1I/7DORjqYitygmyksiTy0q70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Th6xS/btsMmOxVR1I/7DORjqYitygmyksiTy0q70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTh6xS%2FbtsMmOxVR1I%2F7DORjqYitygmyksiTy0q70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1810&quot; height=&quot;1070&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;1021&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XrgVD/btsMkY9YeyC/QtPSNkybRskIpkkZE8pfd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XrgVD/btsMkY9YeyC/QtPSNkybRskIpkkZE8pfd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XrgVD/btsMkY9YeyC/QtPSNkybRskIpkkZE8pfd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXrgVD%2FbtsMkY9YeyC%2FQtPSNkybRskIpkkZE8pfd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1896&quot; height=&quot;1021&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;1021&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;1187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0Fksf/btsMk07HlTl/6VFTxMkLBzwwx5uqtLfkwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0Fksf/btsMk07HlTl/6VFTxMkLBzwwx5uqtLfkwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0Fksf/btsMk07HlTl/6VFTxMkLBzwwx5uqtLfkwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0Fksf%2FbtsMk07HlTl%2F6VFTxMkLBzwwx5uqtLfkwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1480&quot; height=&quot;1187&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;1187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 1번부터 막혀서 시간 다 잡아먹었네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 2번도 1번처럼 난이도가 있어보여서, 그냥 포기했습니다. 남은 시간이 너무 적네요ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;40분이 생각보다 짧다고 느껴지네요.. 처음에 문제를 딱 보고 바로 코드를 깔끔하게 작성하면 시간이 남겠지만..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항을 빼먹거나 생각과는 다른 결과값이 나오거나해서 코드를 계속 수정하다보니, 시간이 금방금방 가네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지 갈 길이 멀어보입니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 재밌어서 시간이 금방 가네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 번 더 풀어보고, Lv.2로 넘어가야겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/558</guid>
      <comments>https://kwonputer.tistory.com/558#entry558comment</comments>
      <pubDate>Mon, 17 Feb 2025 17:48:29 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (5) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/557</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvOEGB/btsMkc1ZUdD/xQxXmJGB56QIVzoUOLdpf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvOEGB/btsMkc1ZUdD/xQxXmJGB56QIVzoUOLdpf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvOEGB/btsMkc1ZUdD/xQxXmJGB56QIVzoUOLdpf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvOEGB%2FbtsMkc1ZUdD%2FxQxXmJGB56QIVzoUOLdpf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;818&quot; height=&quot;328&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkBKaC/btsMlmCw1XU/PPSMwcuOFLwwMUu5IpKZ80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkBKaC/btsMlmCw1XU/PPSMwcuOFLwwMUu5IpKZ80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkBKaC/btsMlmCw1XU/PPSMwcuOFLwwMUu5IpKZ80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkBKaC%2FbtsMlmCw1XU%2FPPSMwcuOFLwwMUu5IpKZ80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;884&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 6분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 포스트에서 풀었던 문제와 비슷한 유형이네요. 소문자 &amp;amp; 대문자의 아스키코드를 알고 있는지를 확인하는 것 같습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/balPCh/btsMlDKOEMk/nMnJxRcYiKlxleokUE0Km0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/balPCh/btsMlDKOEMk/nMnJxRcYiKlxleokUE0Km0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/balPCh/btsMlDKOEMk/nMnJxRcYiKlxleokUE0Km0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbalPCh%2FbtsMlDKOEMk%2FnMnJxRcYiKlxleokUE0Km0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;849&quot; height=&quot;709&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lTN4q/btsMmsPlIXE/d2VSAfofQHIVIl3gv5Hnvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lTN4q/btsMmsPlIXE/d2VSAfofQHIVIl3gv5Hnvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lTN4q/btsMmsPlIXE/d2VSAfofQHIVIl3gv5Hnvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlTN4q%2FbtsMmsPlIXE%2Fd2VSAfofQHIVIl3gv5Hnvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;865&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;865&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 4분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난이도 설정이 이상한데요..? 문제 2번은 계속 어려운 것만 나왔었는데, 이번엔 너무 쉬운 문제가 나왔네요. 이번 문제들은 넘어가고 한번 더 풀겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mA7b9/btsMkBAgqVo/5mNWs72sa7cbNDpVHE6l6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mA7b9/btsMkBAgqVo/5mNWs72sa7cbNDpVHE6l6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mA7b9/btsMkBAgqVo/5mNWs72sa7cbNDpVHE6l6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmA7b9%2FbtsMkBAgqVo%2F5mNWs72sa7cbNDpVHE6l6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;977&quot; height=&quot;671&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dp4Qi/btsMl8p8yLG/YltH6z7wZzik4jPBwxfiYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dp4Qi/btsMl8p8yLG/YltH6z7wZzik4jPBwxfiYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dp4Qi/btsMl8p8yLG/YltH6z7wZzik4jPBwxfiYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDp4Qi%2FbtsMl8p8yLG%2FYltH6z7wZzik4jPBwxfiYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;862&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 2분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 테스트가 이정도 난이도면 공부도 필요없을 것 같은데 말이죠..ㅠ&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;1117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnWtEZ/btsMkZ1VpRL/l6CbFglRJfDaErbA9O96e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnWtEZ/btsMkZ1VpRL/l6CbFglRJfDaErbA9O96e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnWtEZ/btsMkZ1VpRL/l6CbFglRJfDaErbA9O96e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnWtEZ%2FbtsMkZ1VpRL%2Fl6CbFglRJfDaErbA9O96e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1590&quot; height=&quot;1117&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;1117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LUFtI/btsMlmo6qxY/1JBMpuKKgdJGYFhuFiFTtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LUFtI/btsMlmo6qxY/1JBMpuKKgdJGYFhuFiFTtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LUFtI/btsMlmo6qxY/1JBMpuKKgdJGYFhuFiFTtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLUFtI%2FbtsMlmo6qxY%2F1JBMpuKKgdJGYFhuFiFTtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;826&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1493&quot; data-origin-height=&quot;967&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D6EPd/btsMkKcUXK9/GpUTFDvJXFvvRumEp9i4Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D6EPd/btsMkKcUXK9/GpUTFDvJXFvvRumEp9i4Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D6EPd/btsMkKcUXK9/GpUTFDvJXFvvRumEp9i4Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD6EPd%2FbtsMkKcUXK9%2FGpUTFDvJXFvvRumEp9i4Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1493&quot; height=&quot;967&quot; data-origin-width=&quot;1493&quot; data-origin-height=&quot;967&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 시간: 16분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제2는 상당히 쉬운 편이었네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 이번 처럼 쉬운 문제들만 나오니까 아쉽네요ㅠ&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/557</guid>
      <comments>https://kwonputer.tistory.com/557#entry557comment</comments>
      <pubDate>Mon, 17 Feb 2025 17:00:00 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (4) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/556</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;1048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmdThS/btsMlXa6SOE/KqCm4KRPzs3wTk5HUS3qB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmdThS/btsMlXa6SOE/KqCm4KRPzs3wTk5HUS3qB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmdThS/btsMlXa6SOE/KqCm4KRPzs3wTk5HUS3qB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmdThS%2FbtsMlXa6SOE%2FKqCm4KRPzs3wTk5HUS3qB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;972&quot; height=&quot;1048&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;1048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;987&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjTO3F/btsMj6US1MT/kv9II0V6DxzUKyGWskJAeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjTO3F/btsMj6US1MT/kv9II0V6DxzUKyGWskJAeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjTO3F/btsMj6US1MT/kv9II0V6DxzUKyGWskJAeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjTO3F%2FbtsMj6US1MT%2Fkv9II0V6DxzUKyGWskJAeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;987&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;987&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한 문제 1번을 빨리 풀어야하는데, 8분이나 소비해버렸습니다ㅠ&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;1119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oDvdq/btsMlM1OnDs/Fg24KavtR0sMY0VQ1I1fv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oDvdq/btsMlM1OnDs/Fg24KavtR0sMY0VQ1I1fv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oDvdq/btsMlM1OnDs/Fg24KavtR0sMY0VQ1I1fv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoDvdq%2FbtsMlM1OnDs%2FFg24KavtR0sMY0VQ1I1fv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;1119&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;1119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhDogs/btsMkt3pzNg/Ghmg8dG2KMTcMY7sSi1Ck1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhDogs/btsMkt3pzNg/Ghmg8dG2KMTcMY7sSi1Ck1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhDogs/btsMkt3pzNg/Ghmg8dG2KMTcMY7sSi1Ck1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhDogs%2FbtsMkt3pzNg%2FGhmg8dG2KMTcMY7sSi1Ck1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;1136&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1739773918686&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
     * 제도 정착을 위해 오늘부터 일주일 동안 각자 설정한 출근 희망 시각에 늦지 않고 출근한 직원들에게 상품을 주는 이벤트를 진행하려고 합니다.
     *
     * 직원 n명이 설정한 출근 희망 시각을 담은 1차원 정수 배열 schedules, 직원들이 일주일 동안 출근한 시각을 담은 2차원 정수 배열 timelogs,
     * 이벤트를 시작한 요일을 의미하는 정수 startday가 매개변수로 주어집니다.
     * 이때 상품을 받을 직원의 수를 return 하도록 solution 함수를 완성해주세요.
     *
     * - 직원들은 일주일동안 자신이 설정한 출근 희망 시각 + 10분까지 어플로 출근해야 합니다.
     * ===&amp;gt; 단, 토요일, 일요일의 출근 시각은 이벤트에 영향을 끼치지 않습니다.
     * - 직원들은 매일 한 번씩만 어플로 출근하고, 모든 시각은 시에 100을 곱하고 분을 더한 정수로 표현됩니다.
     * ===&amp;gt; 예를 들어 10시 13분은 1013이 되고 9시 58분은 958이 됩니다.
     * - 당신은 직원들이 설정한 출근 희망 시각과 실제로 출근한 기록을 바탕으로 상품을 받을 직원이 몇 명인지 알고 싶습니다
     *
     * 제한사항
     * - 1 &amp;le; schedules의 길이 = n &amp;le; 1,000
     * - schedules[i]는 i + 1번째 직원이 설정한 출근 희망 시각을 의미합니다.
     * - 700 &amp;le; schedules[i] &amp;le; 1100
     * - 1 &amp;le; timelogs의 길이 = n &amp;le; 1,000
     * - timelogs[i]의 길이 = 7
     * - timelogs[i][j]는 i + 1번째 직원이 이벤트 j + 1일차에 출근한 시각을 의미합니다.
     * - 600 &amp;le; timelogs[i][j] &amp;le; 2359
     * - 1 &amp;le; startday &amp;le; 7
     * - 1은 월요일, 2는 화요일, 3은 수요일, 4는 목요일, 5는 금요일, 6은 토요일, 7은 일요일에 이벤트를 시작했음을 의미합니다.
     * - 출근 희망 시각과 실제로 출근한 시각을 100으로 나눈 나머지는 59 이하입니다.
     */
    // schedules: 출근 희망 시간
    // timelogs: 1주일 동안 실제 출근한 시간
    // startday: 이벤트를 시작한 요일
    fun solution(schedules: IntArray, timelogs: Array&amp;lt;IntArray&amp;gt;, startday: Int): Int {
        // 출근 희망 시간 + 10분까지 출근해야 인정
        // 토요일, 일요일은 이벤트에서 제외
        var personCount: Int = 0

        for((scheduleIndex, schedule) in schedules.withIndex()) {
            var isComplete: Boolean = true
            for((logsIndex, logs) in timelogs.withIndex()) {
                if(!isComplete) break
                if(scheduleIndex != logsIndex) continue
                var currentDay = startday
                for(timeLog in logs) {
                    if(!isComplete) break
                    if(currentDay == 6) {
                        continue
                    } else if(currentDay == 7) {
                        currentDay = 1
                        continue
                    }

                    var completeTime = schedule + 10
                    if(completeTime % 100 &amp;gt;= 60) {
                        completeTime -= 60
                        completeTime += 100
                    }

                    if(maxOf(completeTime, timeLog) &amp;gt; completeTime) {
                        isComplete = false
                    }

                    currentDay++
                }
            }

            if(isComplete) {
                personCount++
            }
        }


        return personCount
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으아.. 문제 1번에 시간을 너무 썼나보네요. 간발의 차이로 코드실행을 못해서 이번엔 시간 오버로 못풀었습니다ㅠ&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/556</guid>
      <comments>https://kwonputer.tistory.com/556#entry556comment</comments>
      <pubDate>Mon, 17 Feb 2025 15:35:18 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (3) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/555</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 월요일이네요ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 저번에 이어서 코딩테스트를 해보려고합니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxQNH/btsMjJMczdu/ye1Yk5l1reWODKpZYgQnq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxQNH/btsMjJMczdu/ye1Yk5l1reWODKpZYgQnq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxQNH/btsMjJMczdu/ye1Yk5l1reWODKpZYgQnq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxQNH%2FbtsMjJMczdu%2Fye1Yk5l1reWODKpZYgQnq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;515&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;951&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzkyRv/btsMjM96XWi/6AzicVtly1C417QKu6sgnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzkyRv/btsMjM96XWi/6AzicVtly1C417QKu6sgnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzkyRv/btsMjM96XWi/6AzicVtly1C417QKu6sgnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzkyRv%2FbtsMjM96XWi%2F6AzicVtly1C417QKu6sgnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;951&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;951&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 문제입니다. 약수를 구하는 문제네요. 약수는 나머지가 0이 되는 숫자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제1은 쉬운 난이도만 나오나보네요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7e4Sy/btsMksiLSka/U6AdOHiCRMkQ3XI1BBygA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7e4Sy/btsMksiLSka/U6AdOHiCRMkQ3XI1BBygA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7e4Sy/btsMksiLSka/U6AdOHiCRMkQ3XI1BBygA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7e4Sy%2FbtsMksiLSka%2FU6AdOHiCRMkQ3XI1BBygA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1311&quot; height=&quot;1018&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;1018&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;1063&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxX1OC/btsMk1ESfMR/6ZK4r1f5u0WCZyDFY78e01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxX1OC/btsMk1ESfMR/6ZK4r1f5u0WCZyDFY78e01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxX1OC/btsMk1ESfMR/6ZK4r1f5u0WCZyDFY78e01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxX1OC%2FbtsMk1ESfMR%2F6ZK4r1f5u0WCZyDFY78e01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;703&quot; height=&quot;1063&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;1063&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;983&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFvbF2/btsMmcS9vpo/mzpTGdgzeCazCip6WCB6a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFvbF2/btsMmcS9vpo/mzpTGdgzeCazCip6WCB6a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFvbF2/btsMmcS9vpo/mzpTGdgzeCazCip6WCB6a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFvbF2%2FbtsMmcS9vpo%2FmzpTGdgzeCazCip6WCB6a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;983&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;983&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;563&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n5H5f/btsMkyDljP1/2rxW3nGk2oKKGQQ7tqJFtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n5H5f/btsMkyDljP1/2rxW3nGk2oKKGQQ7tqJFtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n5H5f/btsMkyDljP1/2rxW3nGk2oKKGQQ7tqJFtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn5H5f%2FbtsMkyDljP1%2F2rxW3nGk2oKKGQQ7tqJFtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;563&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;563&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;1147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CRhxg/btsMkLWITvq/nbeYRaXbkNxKUm4JoK61c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CRhxg/btsMkLWITvq/nbeYRaXbkNxKUm4JoK61c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CRhxg/btsMkLWITvq/nbeYRaXbkNxKUm4JoK61c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCRhxg%2FbtsMkLWITvq%2FnbeYRaXbkNxKUm4JoK61c1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;1147&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;1147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에만 35분을 썼네요.. 하물며 채점도중에 시험이 끝나버렸어요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채점하는데 &quot;런타임에러&quot;가 계속 뜨더라구요, 이유를 생각해보니까 'result'의 size를 keymap.size로 설정해놨었는데, targets.size로 설정했어야 했네요ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번부터 계속 문제2가 시간이 부족하네요.. 계속 연습하다보면 나아지겠죠?&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/555</guid>
      <comments>https://kwonputer.tistory.com/555#entry555comment</comments>
      <pubDate>Mon, 17 Feb 2025 14:47:11 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (2) 이전 문제 다시 풀어보기</title>
      <link>https://kwonputer.tistory.com/554</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1027&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eahtvt/btsMheYcgN1/lW17fqHi4BZPHS2b8VkdBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eahtvt/btsMheYcgN1/lW17fqHi4BZPHS2b8VkdBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eahtvt/btsMheYcgN1/lW17fqHi4BZPHS2b8VkdBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feahtvt%2FbtsMheYcgN1%2FlW17fqHi4BZPHS2b8VkdBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;971&quot; height=&quot;1027&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1027&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;749&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QugFN/btsMgJYHP7C/ok6ciU2jvXE2OvBk9m12kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QugFN/btsMgJYHP7C/ok6ciU2jvXE2OvBk9m12kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QugFN/btsMgJYHP7C/ok6ciU2jvXE2OvBk9m12kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQugFN%2FbtsMgJYHP7C%2Fok6ciU2jvXE2OvBk9m12kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;945&quot; height=&quot;749&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;749&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1739420057711&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class ExampleUnitTest {
    fun solution(N: Int, stages: IntArray): IntArray {
        // 결과를 담을 리스트 생성
        val result = ArrayList&amp;lt;Int&amp;gt;()

        // 각 스테이지별 실패율을 저장할 맵 생성
        val failMap = mutableMapOf&amp;lt;Int, Double&amp;gt;()

        // 각 스테이지별로 실패율 계산
        // 1부터 N(마지막 스테이지)까지 반복
        for (i in 1..N) {
            // 스테이지에 도달한 사람 수
            var tryCount = 0

            // 스테이지를 클리어 못한 사람 수
            var failCount = 0

            // stages 배열을 돌면서 도전자 수와 실패자 수 계산
            for (j in stages) {
                if (j &amp;gt;= i) {    // i스테이지에 도달했으면
                    tryCount++
                }
                if (j == i) {    // i스테이지를 클리어하지 못했으면
                    failCount++
                }
            }

            // 실패율 계산해서 맵에 저장
            if (tryCount == 0) {
                failMap[i] = 0.0
            } else {
                failMap[i] = failCount.toDouble() / tryCount
            }
        }

        // 실패율 높은 순으로 스테이지 번호 정렬
        while (failMap.isNotEmpty()) {
            var maxRate = -1.0
            var maxStage = 0

            // 현재 남은 스테이지 중 가장 실패율 높은 스테이지 찾기
            for ((stage, rate) in failMap) {
                if (rate &amp;gt; maxRate) {
                    maxRate = rate
                    maxStage = stage
                } else if (rate == maxRate &amp;amp;&amp;amp; stage &amp;lt; maxStage) {
                    // 실패율이 같다면 작은 번호가 우선
                    maxStage = stage
                }
            }

            // 찾은 스테이지를 결과에 추가하고 맵에서 제거
            result.add(maxStage)
            failMap.remove(maxStage)
        }

        return result.toIntArray()
    }

    @Test
    fun main() {
        // 테스트
        val n1 = 5
        val stages1 = intArrayOf(2, 1, 2, 6, 2, 4, 3, 3)

        // 3, 4, 2, 1, 5
        println(&quot;결과1: ${solution(n1, stages1).contentToString()}&quot;)

        val n2 = 4
        val stages2 = intArrayOf(4, 4, 4, 4, 4)

        // 4, 1, 2, 3
        println(&quot;결과2: ${solution(n2, stages2).contentToString()}&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739420057714&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;결과1: [3, 4, 2, 1, 5]
결과2: [4, 1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에는 저번 코딩테스트에서 풀었던 문제입니다. 딱 기본적인 조건문, 반복문만 사용해서 풀었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 문제를 풀기위해서 반드시 필요한 값을 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 스테이지 갯수&lt;/li&gt;
&lt;li&gt;사용자들이 현재 도전 중인 스테이지 배열&lt;/li&gt;
&lt;li&gt;스테이지에 도달한 플에이어 수 (도전자 수)&lt;/li&gt;
&lt;li&gt;스테이지를 클리어하지 못한 플레이어 수 (실패자 수)&lt;/li&gt;
&lt;li&gt;실패율: 실패자 수 / 도전자 수&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. HashMap 사용&lt;/p&gt;
&lt;pre id=&quot;code_1739423970536&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ExampleUnitTest {
    fun newSolution(N: Int, stages: IntArray): IntArray {
        // 스테이지별 실패자 수
        val stageFailCount: IntArray = IntArray(N + 2) { 0 }
        for(failStage in stages) {
            stageFailCount[failStage]++
        }

        // 총 도전자 수
        var challengers = stages.size

        // 스테이지별 도전자 수
        val stageChallengerMap: HashMap&amp;lt;Int, Int&amp;gt; = hashMapOf()
        for(challengerStage in 1..N) {
            challengers -= stageFailCount[challengerStage]
            stageChallengerMap[challengerStage] = challengers
        }

        // 각 스테이지별 실패율 계산
        // 실패자 수 / 도전자 수
        val stageRateMap: HashMap&amp;lt;Int, Double&amp;gt; = hashMapOf()
        for(stageKey in stageChallengerMap.keys) {
            stageRateMap[stageKey] = stageFailCount[stageKey].toDouble() / stageChallengerMap[stageKey]!!.toDouble()
        }

        // 실패율이 높은 순서대로 정렬
        val sortRateMap = stageRateMap.toList().sortedByDescending { (stage, rate) -&amp;gt; rate }.toMap()

        return sortRateMap.keys.toIntArray()
    }

    @Test
    fun main() {
        // 테스트
        val n1 = 5
        val stages1 = intArrayOf(2, 1, 2, 6, 2, 4, 3, 3)

        // 3, 4, 2, 1, 5
        println(&quot;결과1: ${newSolution(n1, stages1).contentToString()}&quot;)

        val n2 = 4
        val stages2 = intArrayOf(4, 4, 4, 4, 4)

        // 4, 1, 2, 3
        println(&quot;결과2: ${newSolution(n2, stages2).contentToString()}&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap을 사용해서 풀어봤습니다. 막상 풀고보니, 도전자 수와 실패자 수를 따로 변수에 담을 필요가 없을 것 같아서 아래처럼 변경했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 실패율 계산 통합&lt;/p&gt;
&lt;pre id=&quot;code_1739424653388&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun newSolution2(N: Int, stages: IntArray): IntArray {
        // 스테이지별 실패자 수
        val stageFailCount: IntArray = IntArray(N + 2) { 0 }
        for(failStage in stages) {
            stageFailCount[failStage]++
        }

        // 총 도전자 수
        var challengers = stages.size

        // 스테이지별 실패율
        val stageRateMap: HashMap&amp;lt;Int, Double&amp;gt; = hashMapOf()
        for(stage in 1..N) {
            challengers -= stageFailCount[stage]
            stageRateMap[stage] = stageFailCount[stage].toDouble() / challengers.toDouble()
        }

        return stageRateMap
            .toList()
            .sortedByDescending { it.second }
            .toMap()
            .keys
            .toIntArray()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. ArrayList&amp;lt;Pair&amp;gt; 사용&lt;/p&gt;
&lt;pre id=&quot;code_1739424950209&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun newSolution3(N: Int, stages: IntArray): IntArray {
        // 스테이지별 실패자 수
        val stageFailCount: IntArray = IntArray(N + 2) { 0 }
        for(failStage in stages) {
            stageFailCount[failStage]++
        }

        // 총 도전자 수
        var challengers = stages.size

        // 스테이지별 실패율
        val stageRatePair: ArrayList&amp;lt;Pair&amp;lt;Int, Double&amp;gt;&amp;gt; = ArrayList()
        for(stage in 1..N) {
            challengers -= stageFailCount[stage]
            stageRatePair.add(Pair(stage, stageFailCount[stage].toDouble() / challengers.toDouble()))
        }

        return stageRatePair
            .sortedByDescending { it.second }
            .map { it.first }
            .toIntArray()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트는 여기서 마치겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서 프로그래머스 Lv.1을 다시 도전해보겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/554</guid>
      <comments>https://kwonputer.tistory.com/554#entry554comment</comments>
      <pubDate>Thu, 13 Feb 2025 14:36:27 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 문법 - 반복문 &amp;amp; 배열 &amp;amp;  해시 &amp;amp; 정렬</title>
      <link>https://kwonputer.tistory.com/553</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Kotlin 문법에 대해서 다뤄보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 코딩테스트를 한 번 해봤는데, Kotlin 문법을 까먹었더니 확장함수를 쓸 수가 없어서 너무 불편하더라고요, 그래서 이번 포스트를 작성하게 됐습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 반복문에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;반복문&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 기본 for와 범위 연산자(..)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739414231955&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// N값이 5인 경우: 1, 2, 3, 4, 5
for (stage in 1..N) { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 끝 값을 포함합니다. (N값이 5인 경우: 1, 2, 3, 4, 5)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. until&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739414289043&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// N값이 5인 경우: 1, 2, 3, 4
for (stage in 1 until N) { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 끝 값을 포함하지 않습니다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;N값이 5인 경우:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 1, 2, 3, 4)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열의 사이즈만큼 반복할 때 유용해 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. forEach&lt;/p&gt;
&lt;pre id=&quot;code_1739414480743&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(1..N).forEach { stage -&amp;gt; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- continue와 break 사용이 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코드가 간결해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수형 프로그래밍에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. repeat&lt;/p&gt;
&lt;pre id=&quot;code_1739414567334&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repeat(N) { index -&amp;gt; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- continue와 break 사용이 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 0부터 시작하는 인덱스를 제공합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열의 크기 만큼만 반복합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. while&lt;/p&gt;
&lt;pre id=&quot;code_1739414716672&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var stage = 1
while (stage &amp;lt;= N) {
    stage++
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조건에 따른 유연한 반복이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 무한 루프의 위험이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. indices&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739414773209&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (i in stages.indices) {
    val stage = stages[i]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 0부터 시작하는 인덱스를 제공합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열의 크기 만큼만 반복합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- repeat과 동일한 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. withIndex&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739414930464&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for ((index, stage) in stages.withIndex()) { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스와 값을 동시에 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. forEachIndexed&lt;/p&gt;
&lt;pre id=&quot;code_1739414997960&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;stages.forEachIndexed { index, stage -&amp;gt; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스와 값을 동시에 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- forEach와 마찬가지로 함수형 프로그래밍에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. map과 range&lt;/p&gt;
&lt;pre id=&quot;code_1739415157556&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(1..N).map { stage -&amp;gt; }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739415595916&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val stageArray: Array&amp;lt;Int&amp;gt; = arrayOf(6, 7, 4, 2)
val stageCopy = (1..stageArray.size).map { index -&amp;gt;
    stageArray[index - 1] * 2
}
for((index, stage) in stageArray.withIndex()) {
    println(&quot;[원본] index: $index, stage: $stage&quot;)
}
println(&quot;[복제] stageCopy: $stageCopy&quot;)

// [원본] index: 0, stage: 6
// [원본] index: 1, stage: 7
// [원본] index: 2, stage: 4
// [원본] index: 3, stage: 2
// [복제] stageCopy: [12, 14, 8, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 새로운 컬렉션을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원본 데이터는 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. do-while&lt;/p&gt;
&lt;pre id=&quot;code_1739415805462&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var stage = 1
do {
    println(&quot;Stage: $stage&quot;)
    stage++
} while (stage &amp;lt;= N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- while 조건과 관계 없이 최소 1번은 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반복 조건을 마지막에 체크합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. downTo&lt;/p&gt;
&lt;pre id=&quot;code_1739415853104&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 사용법
for (i in N downTo 1) {
    println(i)  // N부터 1까지 역순으로 출력
}

// step 사용
for (i in N downTo 1 step 2) {
    println(i)  // 2칸씩 건너뛰며 역순으로
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 역순 반복을 원할 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- step으로 감소 간격 지정이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 끝 값을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. sequence&lt;/p&gt;
&lt;pre id=&quot;code_1739415993111&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sequence {
    for (stage in 1..N) {
        yield(stage)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지연 평가로 메모리 효율적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 체이닝 연산에 효과적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'sequence'는 추가적인 설명을 더 드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 일반적인&amp;nbsp;처리&amp;nbsp;방식&amp;nbsp;(map,&amp;nbsp;filter&amp;nbsp;사용)&lt;/p&gt;
&lt;pre id=&quot;code_1739416081316&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers
    .map { it * 2 }  // [2, 4, 6, 8, 10] 새로운 리스트 생성
    .filter { it &amp;gt; 5 }  // [6, 8, 10] 또 새로운 리스트 생성
println(result)  // [6, 8, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* sequence&amp;nbsp;처리&amp;nbsp;방식&lt;/p&gt;
&lt;pre id=&quot;code_1739416104732&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers.asSequence()
    .map { it * 2 }  // 아직 계산하지 않음
    .filter { it &amp;gt; 5 }  // 아직 계산하지 않음
    .toList()  // 이때 실제로 계산
println(result)  // [6, 8, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 예시&lt;/p&gt;
&lt;pre id=&quot;code_1739416126392&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일반 처리: 과정 출력해보기
val numbers = listOf(1, 2, 3)
println(&quot;일반 처리 시작&quot;)
val result1 = numbers
    .map { 
        println(&quot;map: $it&quot;)
        it * 2 
    }
    .filter { 
        println(&quot;filter: $it&quot;)
        it &amp;gt; 3 
    }
println(&quot;결과: $result1&quot;)

// sequence 처리: 과정 출력해보기
println(&quot;\nsequence 처리 시작&quot;)
val result2 = numbers.asSequence()
    .map { 
        println(&quot;map: $it&quot;)
        it * 2 
    }
    .filter { 
        println(&quot;filter: $it&quot;)
        it &amp;gt; 3 
    }
    .toList()
println(&quot;결과: $result2&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739416150942&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;일반 처리 시작
map: 1
map: 2
map: 3
filter: 2
filter: 4
filter: 6
결과: [4, 6]

sequence 처리 시작
map: 1
filter: 2
map: 2
filter: 4
map: 3
filter: 6
결과: [4, 6]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;자주 사용할 것 같은 반복문:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;범위 연산자(..), until, indices, withIndex&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 배열에 대해서 다뤄보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;배열&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Array와 arrayOf()&lt;/p&gt;
&lt;pre id=&quot;code_1739416728530&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Array: 크기가 고정된 배열
val arr1 = Array(5) { 0 }  // [0, 0, 0, 0, 0]
val arr2 = Array(3) { it }  // [0, 1, 2]
val arr3 = Array(5) { it * 2 }  // [0, 2, 4, 6, 8]

// arrayOf(): 요소를 직접 지정해서 배열 생성
val arr4 = arrayOf(1, 2, 3)  // [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 크기가 고정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스로 접근합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ArrayList와&amp;nbsp;arrayListOf()&lt;/p&gt;
&lt;pre id=&quot;code_1739417796694&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ArrayList: 동적 크기 리스트
val list1 = ArrayList&amp;lt;Int&amp;gt;()  // 빈 리스트
list1.add(1)  // 요소 추가 가능

// arrayListOf(): 초기 요소와 함께 ArrayList 생성
val list2 = arrayListOf(1, 2, 3)  // [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 크기가 동적으로 변합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 요소의 추가/삭제가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. mutableListOf()&lt;/p&gt;
&lt;pre id=&quot;code_1739417856709&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = mutableListOf(1, 2, 3)
list.add(4)      // 추가 가능
list[0] = 10     // 수정 가능
list.remove(2)   // 삭제 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수정 가능한 리스트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ArrayList와 비슷하지만 더 추상화된 인터페이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 추가적인 설명을 드리겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1739418005939&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ArrayList는 구체적인 구현체를 직접 사용
val arrayList = ArrayList&amp;lt;Int&amp;gt;()
arrayList.add(1)
arrayList.ensureCapacity(100)  // ArrayList의 고유 메서드 사용 가능

// mutableListOf는 MutableList 인터페이스를 통해 사용
val mutableList = mutableListOf&amp;lt;Int&amp;gt;()
mutableList.add(1)
// mutableList.ensureCapacity(100)  // 컴파일 에러, MutableList 인터페이스에 없는 메서드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ArrayList는 실제 구현체를 직접 사용하므로 ArrayList의 모든 구체적인 메서드를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mutableListOf는 MutableList 인터페이스를 통해 접근하므로 인터페이스에 정의된 메서드만 사용이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. IntArray (기본타입 배열)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739418066608&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 크기와 초기값으로 생성
val arr1 = IntArray(5)        // [0, 0, 0, 0, 0]
val arr2 = IntArray(5) { it } // [0, 1, 2, 3, 4]

// 직접 값 지정
val arr3 = intArrayOf(1, 2, 3)  // [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 타입(int) 배열 (참고로 Double 등등 가능합니다. *DoubleArray)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Array&amp;lt;Int&amp;gt;보다&amp;nbsp;성능이&amp;nbsp;좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 차이가 나는 이유에 대해서 좀 더 설명을 드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 메모리 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- IntArray: 순수하게 int 값만 저장 (4바이트 &amp;times; 배열 크기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Array&amp;lt;Int&amp;gt;: 각 요소가 Integer 객체를 참조 (참조 8바이트 + 객체 오버헤드 12바이트 + int 값 4바이트) &amp;times; 배열 크기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*접근 속도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- IntArray: 메모리에 직접 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Array&amp;lt;Int&amp;gt;: 참조를 통해 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서&amp;nbsp;기본&amp;nbsp;타입(Int,&amp;nbsp;Long,&amp;nbsp;Double&amp;nbsp;등)을&amp;nbsp;다룰&amp;nbsp;때는&amp;nbsp;가능한&amp;nbsp;IntArray,&amp;nbsp;LongArray,&amp;nbsp;DoubleArray&amp;nbsp;등을&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;성능상&amp;nbsp;유리합니다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Array &amp;amp; arrayOf: 고정 배열에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ArrayList &amp;amp; arrayListOf: 추가, 삭제가 필요한 배열에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* mutableListOf: 추가, 삭제, 수정이 필요한 배열에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* IntArray: 기본 배열에 적합&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 해시에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;해시&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. HashMap&lt;/p&gt;
&lt;pre id=&quot;code_1739418532390&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HashMap 생성
val scores = hashMapOf&amp;lt;String, Int&amp;gt;()  // 빈 HashMap
val fruits = hashMapOf(
    &quot;사과&quot; to 1000,
    &quot;바나나&quot; to 2000,
    &quot;딸기&quot; to 3000
)

// 요소 추가/수정
scores[&quot;Kim&quot;] = 95
scores[&quot;Lee&quot;] = 88

// 값 가져오기
println(fruits[&quot;사과&quot;])  // 1000
println(fruits.get(&quot;바나나&quot;))  // 2000
println(fruits.getOrDefault(&quot;망고&quot;, 0))  // 0

// 키/값 존재 여부 확인
println(fruits.containsKey(&quot;사과&quot;))  // true
println(fruits.containsValue(1000))  // true

// 요소 제거
fruits.remove(&quot;바나나&quot;)

// 순회하기
for ((key, value) in fruits) {
    println(&quot;$key: $value&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 키&amp;amp;값 쌍으로 데이터를 저장합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;* 각 요소에 대한 추가 정보를 저장해야하는 경우&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 키를 통한 빠른 검색이 필요한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. HashSet&lt;/p&gt;
&lt;pre id=&quot;code_1739418797467&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HashSet 생성
val numberSet = hashSetOf&amp;lt;Int&amp;gt;()  // 빈 HashSet
val fruitsSet = hashSetOf(&quot;사과&quot;, &quot;바나나&quot;, &quot;딸기&quot;)  // 초기값이 있는 HashSet

// 요소 추가
numberSet.add(1)
numberSet.add(2)

// 요소 존재 여부 확인
println(fruitsSet.contains(&quot;사과&quot;))  // true

// 요소 제거
fruitsSet.remove(&quot;바나나&quot;)

// 크기 확인
println(fruitsSet.size)  // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중복 제거가 필요한 경우 매우 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단일 값만 저장합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;* 단순히 고유한 값들의 집합이 필요한 경우&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 집합 연산(교집합, 합집합 등)이 필요한 경우&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 정렬에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;정렬&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. sorted / sortedBy &amp;amp; sort / sortBy&lt;/p&gt;
&lt;pre id=&quot;code_1739419093623&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// sorted(): 오름차순 정렬 (새로운 리스트 반환)
val numbers = listOf(3, 1, 4, 1, 5, 9)
val sortedNumbers = numbers.sorted() // [1, 1, 3, 4, 5, 9]

// sortedDescending(): 내림차순 정렬
val descendingNumbers = numbers.sortedDescending() // [9, 5, 4, 3, 1, 1]

// sort(): 변경 가능한 리스트 직접 정렬
val mutableNumbers = mutableListOf(3, 1, 4, 1, 5, 9)
mutableNumbers.sort() // 리스트 자체가 정렬됨

// sortDescending(): 변경 가능한 리스트 내림차순 정렬
mutableNumbers.sortDescending()&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739419130815&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Person(val name: String, val age: Int)

val people = listOf(
    Person(&quot;Kim&quot;, 25),
    Person(&quot;Lee&quot;, 20),
    Person(&quot;Park&quot;, 30)
)

// sortedBy: 특정 프로퍼티로 정렬
val sortedByAge = people.sortedBy { it.age }
val sortedByName = people.sortedBy { it.name }

// sortedByDescending: 특정 프로퍼티로 내림차순 정렬
val sortedByAgeDesc = people.sortedByDescending { it.age }

// sortedWith: 여러 조건으로 정렬
val sortedWithComparator = people.sortedWith(
    compareBy&amp;lt;Person&amp;gt; { it.age }.thenBy { it.name }
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739419180181&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// compareBy로 복잡한 정렬 조건 만들기
val complexSort = people.sortedWith(
    compareBy(
        { it.name.length }, // 먼저 이름 길이로 정렬
        { it.age },         // 그 다음 나이로 정렬
        { it.name }         // 마지막으로 이름으로 정렬
    )
)

// 널 값 처리
data class User(val name: String, val score: Int?)
val users = listOf(
    User(&quot;Kim&quot;, 80),
    User(&quot;Lee&quot;, null),
    User(&quot;Park&quot;, 90)
)

// nullsLast: null 값을 마지막으로
val sortedWithNulls = users.sortedWith(
    compareBy(nullsLast()) { it.score }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sorted / sortedBy: 새로운 리스트를 생성하므로 메모리를 더 사용하지만, 원본 리스트를 보존합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sort / sortBy: 원본 리스트를 직접 수정하므로 메모리 효율적이지만, 원본이 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. compareBy&lt;/p&gt;
&lt;pre id=&quot;code_1739419489742&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 단일 조건 compareBy
val people = listOf(
    Person(&quot;Kim&quot;, 25),
    Person(&quot;Lee&quot;, 20),
    Person(&quot;Park&quot;, 30)
)

// 나이로 정렬
val sortedByAge = people.sortedWith(compareBy { it.age })
// 결과: [Person(&quot;Lee&quot;, 20), Person(&quot;Kim&quot;, 25), Person(&quot;Park&quot;, 30)]

// 이름으로 정렬
val sortedByName = people.sortedWith(compareBy { it.name })
// 결과: [Person(&quot;Kim&quot;, 25), Person(&quot;Lee&quot;, 20), Person(&quot;Park&quot;, 30)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- compareBy: 정렬 기준을 만드는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. thenBy&lt;/p&gt;
&lt;pre id=&quot;code_1739419505434&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Student(
    val grade: Int,    // 학년
    val name: String,  // 이름
    val score: Int     // 점수
)

val students = listOf(
    Student(2, &quot;Kim&quot;, 85),
    Student(1, &quot;Lee&quot;, 90),
    Student(2, &quot;Park&quot;, 85),
    Student(1, &quot;Choi&quot;, 95)
)

// 1. 학년 순으로 정렬하고, 
// 2. 학년이 같다면 점수로 정렬하고,
// 3. 점수도 같다면 이름순으로 정렬
val sorted = students.sortedWith(
    compareBy&amp;lt;Student&amp;gt; { it.grade }    // 첫 번째 조건
        .thenByDescending { it.score } // 두 번째 조건 (내림차순)
        .thenBy { it.name }           // 세 번째 조건
)

// 결과 출력
sorted.forEach { student -&amp;gt;
    println(&quot;${student.grade}학년 ${student.name}: ${student.score}점&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- thenBy: 첫 번째 조건이 같을 때, 그 다음 조건으로 정렬하고 싶을 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. sortWith&lt;/p&gt;
&lt;pre id=&quot;code_1739419585515&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 기본적인 sortWith 사용
val numbers = mutableListOf(3, 1, 4, 1, 5, 9)
numbers.sortWith(compareBy { it })  // [1, 1, 3, 4, 5, 9]

// 2. 객체 정렬
data class Person(val name: String, val age: Int)

val people = mutableListOf(
    Person(&quot;Kim&quot;, 25),
    Person(&quot;Lee&quot;, 20),
    Person(&quot;Park&quot;, 30)
)

// 나이순으로 정렬
people.sortWith(compareBy { it.age })

// 여러 조건으로 정렬 (나이 오름차순, 이름 내림차순)
people.sortWith(
    compareBy&amp;lt;Person&amp;gt; { it.age }
        .thenByDescending { it.name }
)

// null을 처음에 위치시키기
val nullsFirst = people.sortedWith(
    compareBy(
        nullsFirst() { it.age },  // null이 앞으로
        { it.name }
    )
)

// null을 마지막에 위치시키기
val nullsLast = people.sortedWith(
    compareBy(
        nullsLast() { it.age },   // null이 뒤로
        { it.name }
    )
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sortWith: 커스텀 비교자(Comparator)를 사용하여 MutableList를 정렬하는 함수입니다. 원본 리스트를 직접 정렬합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 확장 함수에 대해서 다루고 마치겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #7e98b1;&quot;&gt;&lt;b&gt;확장 함수&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 배열 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419822856&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// toTypedArray() : 컬렉션을 배열로 변환
val list = listOf(1, 2, 3)
val array = list.toTypedArray() // Array&amp;lt;Int&amp;gt;

// toIntArray(), toDoubleArray() 등: 기본 타입 배열로 변환
val primitiveArray = list.toIntArray() // IntArray

// contentToString() : 배열의 내용을 문자열로 변환
println(array.contentToString()) // [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 컬렉션 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419838594&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// getOrDefault() : 키가 없을 때 기본값 반환
val map = mapOf(&quot;a&quot; to 1, &quot;b&quot; to 2)
val value = map.getOrDefault(&quot;c&quot;, 0) // 0

// getOrElse() : 키가 없을 때 람다 실행
val value2 = map.getOrElse(&quot;c&quot;) { 
    println(&quot;키를 찾을 수 없습니다&quot;)
    0 
}

// orEmpty() : null일 경우 빈 컬렉션 반환
val nullableList: List&amp;lt;Int&amp;gt;? = null
val safeList = nullableList.orEmpty() // 빈 리스트 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 변환 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419849900&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// map : 각 요소를 변환
val numbers = listOf(1, 2, 3)
val doubled = numbers.map { it * 2 } // [2, 4, 6]

// mapNotNull : null을 제외하고 변환
val mixed = listOf(1, null, 2, null, 3)
val nonNull = mixed.mapNotNull { it } // [1, 2, 3]

// flatten : 중첩 컬렉션을 단일 레벨로
val nested = listOf(listOf(1, 2), listOf(3, 4))
val flat = nested.flatten() // [1, 2, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 필터링 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419861495&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// filter : 조건에 맞는 요소만 선택
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 } // [2, 4]

// filterNot : 조건에 맞지 않는 요소 선택
val odds = numbers.filterNot { it % 2 == 0 } // [1, 3, 5]

// filterNotNull : null이 아닌 요소만 선택
val withNull = listOf(1, null, 2, null, 3)
val noNulls = withNull.filterNotNull() // [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 검색 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419871176&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// find : 조건에 맞는 첫 요소 찾기
val numbers = listOf(1, 2, 3, 4, 5)
val firstEven = numbers.find { it % 2 == 0 } // 2

// firstOrNull : 첫 요소 또는 null
val firstBig = numbers.firstOrNull { it &amp;gt; 10 } // null

// count : 조건에 맞는 요소 개수
val evenCount = numbers.count { it % 2 == 0 } // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 문자열 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419880931&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// capitalize : 첫 글자를 대문자로
val str = &quot;hello&quot;
println(str.capitalize()) // &quot;Hello&quot;

// trimMargin : 여러 줄 문자열의 마진 제거
val text = &quot;&quot;&quot;
    |First line
    |Second line
&quot;&quot;&quot;.trimMargin()

// split : 문자열 분할
val parts = &quot;a,b,c&quot;.split(&quot;,&quot;) // [a, b, c]

// removeSurrounding : 앞뒤 문자열 제거
println(&quot;&amp;lt;&amp;lt;Hello&amp;gt;&amp;gt;&quot;.removeSurrounding(&quot;&amp;lt;&amp;lt;&quot;, &quot;&amp;gt;&amp;gt;&quot;)) // Hello

// replaceFirst : 첫 번째 일치 항목만 교체
println(&quot;hello hello&quot;.replaceFirst(&quot;hello&quot;, &quot;hi&quot;)) // hi hello

// lines : 줄 단위로 분리
val multiline = &quot;&quot;&quot;
    First
    Second
    Third
&quot;&quot;&quot;.trimIndent()
println(multiline.lines()) // [First, Second, Third]

// padStart, padEnd : 문자열 패딩
println(&quot;123&quot;.padStart(5, '0')) // 00123
println(&quot;123&quot;.padEnd(5, '0')) // 12300&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 컬렉션 처리 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419899606&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// distinct : 중복 제거
val numbers = listOf(1, 1, 2, 2, 3)
println(numbers.distinct()) // [1, 2, 3]

// chunked : n개씩 묶기
val list = (1..7).toList()
println(list.chunked(3)) // [[1, 2, 3], [4, 5, 6], [7]]

// windowed : 슬라이딩 윈도우
println(list.windowed(3)) // [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]]

// groupBy : 그룹화
val people = listOf(
    Person(&quot;Kim&quot;, 20),
    Person(&quot;Lee&quot;, 25),
    Person(&quot;Park&quot;, 20)
)
val byAge = people.groupBy { it.age }

// associate : Map 생성
val mapped = people.associate { it.name to it.age }

// takeIf : 조건을 만족할 때만 값 반환
val number = 10
val evenNumber = number.takeIf { it % 2 == 0 } // 10
val oddNumber = number.takeIf { it % 2 == 1 } // null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 널 안전성 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419912911&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// let : null이 아닐 때 코드 블록 실행
val nullableString: String? = &quot;Hello&quot;
nullableString?.let { 
    println(it.length) 
}

// also : 객체를 반환하면서 부가 작업
val numbers = mutableListOf&amp;lt;Int&amp;gt;()
    .also { println(&quot;생성된 리스트: $it&quot;) }
    .also { it.add(1) }
    .also { println(&quot;숫자 추가 후: $it&quot;) }

// run : 객체의 함수나 프로퍼티 접근
val str = &quot;Hello&quot;
val length = str.run { 
    uppercase().length 
}

// apply : 객체 설정 후 반환
val person = Person().apply {
    name = &quot;Kim&quot;
    age = 25
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 수치 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419961928&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// coerceIn : 범위 내로 제한
val number = 5
println(number.coerceIn(0..10)) // 5
println(number.coerceIn(6..10)) // 6
println(number.coerceIn(0..4)) // 4

// rangeTo : 범위 생성
val range = 1.rangeTo(5) // 1..5

// step : 특정 간격으로 반복
(0..10 step 2).forEach { print(it) } // 0 2 4 6 8 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 시퀀스 관련 확장 함수&lt;/p&gt;
&lt;pre id=&quot;code_1739419974066&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// asSequence : 지연 평가 시퀀스로 변환
val result = listOf(1, 2, 3)
    .asSequence()
    .map { it * 2 }
    .filter { it &amp;gt; 3 }
    .toList()

// generateSequence : 시퀀스 생성
val fibonacci = generateSequence(Pair(0, 1)) { 
    Pair(it.second, it.first + it.second) 
}.map { it.first }

println(fibonacci.take(6).toList()) // [0, 1, 1, 2, 3, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트는 여기서 마치겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트는, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이번 지식을 활용해서&lt;span&gt; 저번에 풀었던 &lt;/span&gt;&lt;/span&gt;코딩테스트 문제를 다시 도전하는 글을 올리겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/553</guid>
      <comments>https://kwonputer.tistory.com/553#entry553comment</comments>
      <pubDate>Thu, 13 Feb 2025 13:17:30 +0900</pubDate>
    </item>
    <item>
      <title>[Coding Test] (1) 프로그래머스 스킬체크 Lv.1</title>
      <link>https://kwonputer.tistory.com/552</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;블로그는 비영리로 운영되고 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;차후 저작권 문제가 발생할 시, 해당 글은 삭제하도록 하겠습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘부터 코딩테스트 연습을 해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음이라서 많이 헤메겠지만 꾸준히 하다보면 나아질거라고 믿습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/skill_checks&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/skill_checks&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739337153224&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/skill_checks&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cBj801/hyYcaqqPCJ/uokYEgMmZQVQDvO35EyZEk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/skill_checks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/skill_checks&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cBj801/hyYcaqqPCJ/uokYEgMmZQVQDvO35EyZEk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmCZNQ/btsMewlIQOK/P4umThO4c0jjExB99mQES1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmCZNQ/btsMewlIQOK/P4umThO4c0jjExB99mQES1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmCZNQ/btsMewlIQOK/P4umThO4c0jjExB99mQES1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmCZNQ%2FbtsMewlIQOK%2FP4umThO4c0jjExB99mQES1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1205&quot; height=&quot;721&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz18O6/btsMgQP1VPS/C7gn9UrdEaBj4sKikiTfc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz18O6/btsMgQP1VPS/C7gn9UrdEaBj4sKikiTfc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz18O6/btsMgQP1VPS/C7gn9UrdEaBj4sKikiTfc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz18O6%2FbtsMgQP1VPS%2FC7gn9UrdEaBj4sKikiTfc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;671&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 레벨 1부터 시작해봅시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2513&quot; data-origin-height=&quot;1290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmEbQx/btsMemDDPXO/fTvk1af0LG9w4pCluVzGq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmEbQx/btsMemDDPXO/fTvk1af0LG9w4pCluVzGq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmEbQx/btsMemDDPXO/fTvk1af0LG9w4pCluVzGq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmEbQx%2FbtsMemDDPXO%2FfTvk1af0LG9w4pCluVzGq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2513&quot; height=&quot;1290&quot; data-origin-width=&quot;2513&quot; data-origin-height=&quot;1290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccmVYX/btsMfaWQMW9/EciIMlknbD5B2NQurgQMM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccmVYX/btsMfaWQMW9/EciIMlknbD5B2NQurgQMM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccmVYX/btsMfaWQMW9/EciIMlknbD5B2NQurgQMM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccmVYX%2FbtsMfaWQMW9%2FEciIMlknbD5B2NQurgQMM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;309&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 상상하던 코딩테스트랑은 뭔가 좀 다르네요! 입출력 예시가 있는점부터 난이도가 많이 내려가네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항은 's는 길이가 1 이상, 100이하인 스트링입니다.' 이부분을 조건문으로 체크하라는 뜻인지, 아니면 항상 저 값이 넘어올테니 생각하고 코딩을 짜라는건지 모르겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mFxkO/btsMf58dIvI/CJhusg9Bsb1viZtNmdpxBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mFxkO/btsMf58dIvI/CJhusg9Bsb1viZtNmdpxBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mFxkO/btsMf58dIvI/CJhusg9Bsb1viZtNmdpxBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmFxkO%2FbtsMf58dIvI%2FCJhusg9Bsb1viZtNmdpxBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;227&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 바로 생각난건 길이로 짜르고, 문자열 가져오기 입니다. 한번 시도해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/19rgA/btsMflw2FgI/es01M7NKdFfKXp4B4ncHB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/19rgA/btsMflw2FgI/es01M7NKdFfKXp4B4ncHB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/19rgA/btsMflw2FgI/es01M7NKdFfKXp4B4ncHB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F19rgA%2FbtsMflw2FgI%2Fes01M7NKdFfKXp4B4ncHB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;273&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIVIGY/btsMgrXy2DS/aVtYYTFvgpm1Nmy5wwcClK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIVIGY/btsMgrXy2DS/aVtYYTFvgpm1Nmy5wwcClK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIVIGY/btsMgrXy2DS/aVtYYTFvgpm1Nmy5wwcClK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIVIGY%2FbtsMgrXy2DS%2FaVtYYTFvgpm1Nmy5wwcClK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;309&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앗, 문제를 읽어보니까 제가 놓친 부분이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;'단어의 길이가 짝수라면 가운데 두글자를 반환하면 됩니다.'&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 처음에 문자열 길이를 체크하고, 문자열 길이가 짝수인 경우에만 조건문을 실행하면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;969&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7MIk3/btsMfgCHqMh/rhYcXFjn152zzk6WyjTAK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7MIk3/btsMfgCHqMh/rhYcXFjn152zzk6WyjTAK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7MIk3/btsMfgCHqMh/rhYcXFjn152zzk6WyjTAK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7MIk3%2FbtsMfgCHqMh%2FrhYcXFjn152zzk6WyjTAK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1153&quot; height=&quot;969&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;969&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분.. 제가 깜빡했네요. Kotlin에는 삼항 연산자가 없습니다! 다시 하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;1053&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XnoyF/btsMferlYuz/PFpVyzK8c8ZOnJ9TabtkT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XnoyF/btsMferlYuz/PFpVyzK8c8ZOnJ9TabtkT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XnoyF/btsMferlYuz/PFpVyzK8c8ZOnJ9TabtkT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXnoyF%2FbtsMferlYuz%2FPFpVyzK8c8ZOnJ9TabtkT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;1053&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;1053&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이대로 제출하기전에 그래도 추가 조건문만 작업해서 제출하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OhPoK/btsMeJSWyKW/sBwfdnG2k6f3IhsLGIKBCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OhPoK/btsMeJSWyKW/sBwfdnG2k6f3IhsLGIKBCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OhPoK/btsMeJSWyKW/sBwfdnG2k6f3IhsLGIKBCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOhPoK%2FbtsMeJSWyKW%2FsBwfdnG2k6f3IhsLGIKBCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1124&quot; height=&quot;508&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니.. 제가 놓친 부분이 더 있었네요. 문제가 1개가 아니라 2개였습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx47X8/btsMfYOVUSu/nSqOf0KKkEahbSI6VFor8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx47X8/btsMfYOVUSu/nSqOf0KKkEahbSI6VFor8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx47X8/btsMfYOVUSu/nSqOf0KKkEahbSI6VFor8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx47X8%2FbtsMfYOVUSu%2FnSqOf0KKkEahbSI6VFor8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;249&quot; height=&quot;53&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KwhAv/btsMgh8zkrO/6SUtPAkXEDIMB4WsMQK4Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KwhAv/btsMgh8zkrO/6SUtPAkXEDIMB4WsMQK4Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KwhAv/btsMgh8zkrO/6SUtPAkXEDIMB4WsMQK4Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKwhAv%2FbtsMgh8zkrO%2F6SUtPAkXEDIMB4WsMQK4Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;1120&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZB2i7/btsMfGud0Ep/Ms4W9FnEr1rlOWLchW4gc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZB2i7/btsMfGud0Ep/Ms4W9FnEr1rlOWLchW4gc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZB2i7/btsMfGud0Ep/Ms4W9FnEr1rlOWLchW4gc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZB2i7%2FbtsMfGud0Ep%2FMs4W9FnEr1rlOWLchW4gc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;971&quot; height=&quot;972&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;1059&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbOKtM/btsMey4YXZU/yNUKc8eWFruvSaeJ6hvCf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbOKtM/btsMey4YXZU/yNUKc8eWFruvSaeJ6hvCf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbOKtM/btsMey4YXZU/yNUKc8eWFruvSaeJ6hvCf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbOKtM%2FbtsMey4YXZU%2FyNUKc8eWFruvSaeJ6hvCf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1182&quot; height=&quot;1059&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;1059&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqs1f/btsMfD5h265/KlLPgoxzu5mDcUswA4Nt41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqs1f/btsMfD5h265/KlLPgoxzu5mDcUswA4Nt41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqs1f/btsMfD5h265/KlLPgoxzu5mDcUswA4Nt41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnqs1f%2FbtsMfD5h265%2FKlLPgoxzu5mDcUswA4Nt41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;735&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XZ5tO/btsMe9wTYkd/JEincN8ueOfZminjz1nYm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XZ5tO/btsMe9wTYkd/JEincN8ueOfZminjz1nYm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XZ5tO/btsMe9wTYkd/JEincN8ueOfZminjz1nYm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXZ5tO%2FbtsMe9wTYkd%2FJEincN8ueOfZminjz1nYm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;892&quot; height=&quot;1078&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그를 쓰면서 코딩테스트를 하니까 시간이 금방금방 가네요. 이번에는 뭐가 상당히 많네요. 일단 위에서부터 천천히 읽어보기 전에! 이 문제에서 원하는 결과값이 뭔지 파악부터 해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rszJY/btsMgSG8JvM/jMI6Zs27dAkmcwqFkufDR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rszJY/btsMgSG8JvM/jMI6Zs27dAkmcwqFkufDR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rszJY/btsMgSG8JvM/jMI6Zs27dAkmcwqFkufDR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrszJY%2FbtsMgSG8JvM%2FjMI6Zs27dAkmcwqFkufDR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;573&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv4QAx/btsMevtOwmP/Vz3hFVHAzOxpZXf8U1AJG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv4QAx/btsMevtOwmP/Vz3hFVHAzOxpZXf8U1AJG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv4QAx/btsMevtOwmP/Vz3hFVHAzOxpZXf8U1AJG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv4QAx%2FbtsMevtOwmP%2FVz3hFVHAzOxpZXf8U1AJG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;524&quot; height=&quot;166&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;survey의 원소는 &quot;RT&quot;, &quot;TR&quot;, &quot;FC&quot;, &quot;CF&quot;, &quot;MJ&quot;, &quot;JM&quot;, &quot;AN&quot;, &quot;NA&quot; 중 하나입니다.&lt;/li&gt;
&lt;li&gt;survey[i]의 첫 번째 캐릭터는 i+1번 질문의 비동의 관련 선택지를 선택하면 받는 성격 유형을 의미합니다.&lt;/li&gt;
&lt;li&gt;survey[i]의 두 번째 캐릭터는 i+1번 질문의 동의 관련 선택지를 선택하면 받는 성격 유형을 의미합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'survey'는 설문조사에 사용되는 단어입니다. 즉, 설문조사 관련된 코드네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2번과 3번은 무슨 말인지 와닿지않네요. &quot;RT&quot;에서 첫 번째, 두 번째 글자를 뜻하는건지.. 이건 글을 좀 더 읽다보면 보일 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 문제설명부터 파악해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bghOGm/btsMeKjXTPc/aqX1OeG7jepwEAUhPKMUW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bghOGm/btsMeKjXTPc/aqX1OeG7jepwEAUhPKMUW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bghOGm/btsMeKjXTPc/aqX1OeG7jepwEAUhPKMUW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbghOGm%2FbtsMeKjXTPc%2FaqX1OeG7jepwEAUhPKMUW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;995&quot; height=&quot;288&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설문조사가 아니라, 성격유형 검사 내용이었네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OlxdV/btsMfb9mGvp/9VgSK4qziAuiWZrQEMICLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OlxdV/btsMfb9mGvp/9VgSK4qziAuiWZrQEMICLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OlxdV/btsMfb9mGvp/9VgSK4qziAuiWZrQEMICLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOlxdV%2FbtsMfb9mGvp%2F9VgSK4qziAuiWZrQEMICLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1293&quot; height=&quot;697&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 좀 와닿네요. MBTI 검사랑 유사하네요. 약관 동의는 +1점, 매우 비동의는 -3점이라는 뜻이네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1329&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qS0B4/btsMgtVqK9t/nn4YNRHsmk79RiGcavOnVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qS0B4/btsMgtVqK9t/nn4YNRHsmk79RiGcavOnVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qS0B4/btsMgtVqK9t/nn4YNRHsmk79RiGcavOnVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqS0B4%2FbtsMgtVqK9t%2Fnn4YNRHsmk79RiGcavOnVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1329&quot; height=&quot;350&quot; data-origin-width=&quot;1329&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'위 예시처럼 네오형이 비동의, 어피치형이 동의인 경우만 주어지지 않고, 질문에 따라 네오형이 동의, 어피치형이 비동의인 경우도 주어질 수 있습니다.' 이것도 중요한 문장이네요. 단순히 +, -가 아니라, 문제에 따라서 다르게 해석 될 수 있다고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zED5u/btsMey408IA/0PgOnzTGp02cms3boMjWG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zED5u/btsMey408IA/0PgOnzTGp02cms3boMjWG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zED5u/btsMey408IA/0PgOnzTGp02cms3boMjWG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzED5u%2FbtsMey408IA%2F0PgOnzTGp02cms3boMjWG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1186&quot; height=&quot;432&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 문제에서는 (-) 선택지를 선택하면, 'A'형 성격 유형의 점수를, (+) 선택지를 선택하면 'N'형 성격 유형 점수를 받습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7dhJL/btsMgqYJ0p1/JiEMMKrnoGgLetmPtxyEAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7dhJL/btsMgqYJ0p1/JiEMMKrnoGgLetmPtxyEAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7dhJL/btsMgqYJ0p1/JiEMMKrnoGgLetmPtxyEAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7dhJL%2FbtsMgqYJ0p1%2FJiEMMKrnoGgLetmPtxyEAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;719&quot; height=&quot;368&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 좀 와닿네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YQfKR/btsMeShT6U5/MDRqz71KkPzarSf9QGttg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YQfKR/btsMeShT6U5/MDRqz71KkPzarSf9QGttg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YQfKR/btsMeShT6U5/MDRqz71KkPzarSf9QGttg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYQfKR%2FbtsMeShT6U5%2FMDRqz71KkPzarSf9QGttg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;170&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 survey의 각각의 요소는 어떠한 성격 유형 검사인지 말해줍니다. 'AN'에서 5를 선택하면, '약간 동의'이기 때문에 'N' 성격유형 +1점이라는 뜻이겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바탕으로 코드를 작성해봅시다. 블로그 글과 같이 하려니까 시간이 8분밖에 안남았네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;107&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oXRM8/btsMfGA0psR/k6kykr8kmiYALYTHio8JW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oXRM8/btsMfGA0psR/k6kykr8kmiYALYTHio8JW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oXRM8/btsMfGA0psR/k6kykr8kmiYALYTHio8JW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoXRM8%2FbtsMfGA0psR%2Fk6kykr8kmiYALYTHio8JW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;107&quot; height=&quot;60&quot; data-origin-width=&quot;107&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;급하니까 얼른 해봅시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dg49OG/btsMgtHX6iU/wLVDRVMSPTL8O1kYkOfNCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dg49OG/btsMgtHX6iU/wLVDRVMSPTL8O1kYkOfNCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dg49OG/btsMgtHX6iU/wLVDRVMSPTL8O1kYkOfNCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdg49OG%2FbtsMgtHX6iU%2FwLVDRVMSPTL8O1kYkOfNCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;340&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앗! 시간을 못지켜서 종료가 되버렸어요ㅠㅠ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;983&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK5eAw/btsMgrDnEGf/xKsUbNZXtol5jssjAKP461/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK5eAw/btsMgrDnEGf/xKsUbNZXtol5jssjAKP461/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK5eAw/btsMgrDnEGf/xKsUbNZXtol5jssjAKP461/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK5eAw%2FbtsMgrDnEGf%2FxKsUbNZXtol5jssjAKP461%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1296&quot; height=&quot;983&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;983&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 알았는데, 밑에 제출하기는 문제마다 눌러줬어야 하는거네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 도전해보겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJAoxi/btsMgi7yB8K/k8IFPx2kj4Zqw1ZPOKlZz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJAoxi/btsMgi7yB8K/k8IFPx2kj4Zqw1ZPOKlZz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJAoxi/btsMgi7yB8K/k8IFPx2kj4Zqw1ZPOKlZz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJAoxi%2FbtsMgi7yB8K%2Fk8IFPx2kj4Zqw1ZPOKlZz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;953&quot; height=&quot;520&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vqaqx/btsMfX3EY92/Q0vm1BftdaEVlqiCQwnJw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vqaqx/btsMfX3EY92/Q0vm1BftdaEVlqiCQwnJw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vqaqx/btsMfX3EY92/Q0vm1BftdaEVlqiCQwnJw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvqaqx%2FbtsMfX3EY92%2FQ0vm1BftdaEVlqiCQwnJw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;179&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 기억하기로는 소문자는 &quot;97 ~ 122&quot;, 대문자는 &quot;65 ~ 90&quot;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨리빨리 해보도록하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcCxVM/btsMgJcHNjv/6zoa5XrP1g2QlZsAmJza21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcCxVM/btsMgJcHNjv/6zoa5XrP1g2QlZsAmJza21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcCxVM/btsMgJcHNjv/6zoa5XrP1g2QlZsAmJza21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcCxVM%2FbtsMgJcHNjv%2F6zoa5XrP1g2QlZsAmJza21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;955&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;955&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxdsmI/btsMfYBuHhq/M3owm0h62pXnx2uHIKpDx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxdsmI/btsMfYBuHhq/M3owm0h62pXnx2uHIKpDx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxdsmI/btsMfYBuHhq/M3owm0h62pXnx2uHIKpDx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxdsmI%2FbtsMfYBuHhq%2FM3owm0h62pXnx2uHIKpDx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;420&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앗, 공백에 대한 처리를 못했네요. 다시 수정하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;915&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4lfsl/btsMeUNDt9o/TyoUDkRKnJuWh4N8KZ5Ikk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4lfsl/btsMeUNDt9o/TyoUDkRKnJuWh4N8KZ5Ikk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4lfsl/btsMeUNDt9o/TyoUDkRKnJuWh4N8KZ5Ikk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4lfsl%2FbtsMeUNDt9o%2FTyoUDkRKnJuWh4N8KZ5Ikk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;915&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;915&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zvwGc/btsMeT8Y7af/2keO3T3TcRARL7NdFwrVH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zvwGc/btsMeT8Y7af/2keO3T3TcRARL7NdFwrVH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zvwGc/btsMeT8Y7af/2keO3T3TcRARL7NdFwrVH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzvwGc%2FbtsMeT8Y7af%2F2keO3T3TcRARL7NdFwrVH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;550&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u5Zs9/btsMfgv44ra/29hh1gdT5oitEBjdjlZIUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u5Zs9/btsMfgv44ra/29hh1gdT5oitEBjdjlZIUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u5Zs9/btsMfgv44ra/29hh1gdT5oitEBjdjlZIUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu5Zs9%2FbtsMfgv44ra%2F29hh1gdT5oitEBjdjlZIUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;602&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채점 후 결과를 누르니까 위 처럼 내부적으로 뭔가를 하나보네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 통과해서 다음 문제로 넘어가겠습니다. 시간은 30분정도 남았네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1027&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFckZS/btsMgufOkWY/YjfSDUmhLegVS7D3c3u2z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFckZS/btsMgufOkWY/YjfSDUmhLegVS7D3c3u2z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFckZS/btsMgufOkWY/YjfSDUmhLegVS7D3c3u2z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFckZS%2FbtsMgufOkWY%2FYjfSDUmhLegVS7D3c3u2z1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;971&quot; height=&quot;1027&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1027&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;749&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by16BT/btsMfbauStS/H9SAGVKCrlD1KSx65sRMoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by16BT/btsMfbauStS/H9SAGVKCrlD1KSx65sRMoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by16BT/btsMfbauStS/H9SAGVKCrlD1KSx65sRMoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby16BT%2FbtsMfbauStS%2FH9SAGVKCrlD1KSx65sRMoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;945&quot; height=&quot;749&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;749&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임에서 각각 스테이지별로 도전자 수와 실패자 수를 가지고 실패율을 계산하라는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'실패율이 높은 스테이지부터 내림차순으로 스테이지의 번호가 담겨있는 배열을 return 하도록 solution 함수를 완성하라.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게.. 1단계..? 글을 작성하다보니 27분정도 시간이 남았는데, 시간내에 풀 수 있겠죠..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;997&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oIVZG/btsMfeZmWuw/NDBOB7UZ5wNhKbCOdUQRU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oIVZG/btsMfeZmWuw/NDBOB7UZ5wNhKbCOdUQRU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oIVZG/btsMfeZmWuw/NDBOB7UZ5wNhKbCOdUQRU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoIVZG%2FbtsMfeZmWuw%2FNDBOB7UZ5wNhKbCOdUQRU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;997&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;997&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doW2UP/btsMfif8Z0X/mQGwI3K3wZUhz9BbnkClEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doW2UP/btsMfif8Z0X/mQGwI3K3wZUhz9BbnkClEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doW2UP/btsMfif8Z0X/mQGwI3K3wZUhz9BbnkClEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoW2UP%2FbtsMfif8Z0X%2FmQGwI3K3wZUhz9BbnkClEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;559&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하다보니 생각보다 난이도가 있는데요..? 처음에는 스테이지 클리어에 대한 부분을 까먹고 코드를 짰다가 다시 수정했습니다ㅠ 시간이 촉박해서 실패한 스샷도 못찍었네요..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1473&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJazkY/btsMeKEoC0Q/OgNBX8TMiKM6Hqrs3msk60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJazkY/btsMeKEoC0Q/OgNBX8TMiKM6Hqrs3msk60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJazkY/btsMeKEoC0Q/OgNBX8TMiKM6Hqrs3msk60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJazkY%2FbtsMeKEoC0Q%2FOgNBX8TMiKM6Hqrs3msk60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1473&quot; height=&quot;603&quot; data-origin-width=&quot;1473&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhfelp/btsMeKLhcO5/OdXCP4e7Z7GP6k2o6eqFJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhfelp/btsMeKLhcO5/OdXCP4e7Z7GP6k2o6eqFJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhfelp/btsMeKLhcO5/OdXCP4e7Z7GP6k2o6eqFJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhfelp%2FbtsMeKLhcO5%2FOdXCP4e7Z7GP6k2o6eqFJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1482&quot; height=&quot;666&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 심정이 느껴지시나요..? 정말 촉박했습니다. 아니 갑자기 왜 이번 체점은 테스트가 27번까지 있는건지.. 진짜 조마조마했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;1001&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ9qyo/btsMgSUNKf8/VlKmnWerxGulkSD0XJDQ80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ9qyo/btsMgSUNKf8/VlKmnWerxGulkSD0XJDQ80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ9qyo/btsMgSUNKf8/VlKmnWerxGulkSD0XJDQ80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ9qyo%2FbtsMgSUNKf8%2FVlKmnWerxGulkSD0XJDQ80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1339&quot; height=&quot;1001&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;1001&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그나저나 코딩테스트.. 제 생각보다 더 재밌는데요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 코딩테스트에서는 아쉬운 점을 뽑자면, 제가 아직 Kotlin 문법에는 익숙하지 않아서 다양한 메서드를 못써본게 아쉽네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당분간은 Lv.1만 도전해야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는, 이번 문제를 가지고 더 나은 방법이 없는지 좀 더 고민해보는 글을 작성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 했던 문제2번이 계속 맘에 걸리네요. 문제를 이해하는데만 시간이 훌쩍 지나버렸어요ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 책을 많이 읽는편이라고 생각해서 속독이나 해독에 관해서 자신이 있었는데.. 자신감이 확 떨어져버렸어요. 코딩이 문제가 아니라 문제부터 잘 해석하는게 먼저네요ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코딩테스트 포스트를 작성하기 전에, Kotlin 문법에 대한 포스트를 먼저 작성하고 시작하겠습니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문, 반복문, 문자열, 해시, 정렬에 관해서 다루겠습니다. 이걸 먼저 하고 시작하는게 맞을 것 같네요!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/552</guid>
      <comments>https://kwonputer.tistory.com/552#entry552comment</comments>
      <pubDate>Wed, 12 Feb 2025 15:50:09 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] Flutter로 비교하는 Kotlin &amp;amp; Compose 지식</title>
      <link>https://kwonputer.tistory.com/551</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739230113089&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rsbZH/hyYfXbFt57/iJDqGecXHNoCprQq82Khd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Buwcf/hyYcgKLw99/xjWxyc2NyWV6i2Cj9OQx4k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rsbZH/hyYfXbFt57/iJDqGecXHNoCprQq82Khd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Buwcf/hyYcgKLw99/xjWxyc2NyWV6i2Cj9OQx4k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이번 포스트에서는 몇 개월만에 Kotlin으로 다시 개발하고, 또 Compose를 다루면서 습득한 새로운 지식과 다시 기억해낸 중요한 지식 몇 가지를 간단하게 이야기해볼까 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다만, 제가 최근까지 Flutter로 개발을 했기 때문에 Flutter로 비교하면서 설명해볼까 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;먼저 제가 다룰 목록은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@Composable&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Modifier&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@OptIn&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;MutableStateFlow&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Flow&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;invoke&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Result&amp;lt;out T&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;viewModelScope&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;lauch&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CoroutineScope&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Runblocking&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;sealed class&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;@Composable&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A6ffM/btsMcpmzjWm/lGenpGsZP0zdTbtSCozfJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A6ffM/btsMcpmzjWm/lGenpGsZP0zdTbtSCozfJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A6ffM/btsMcpmzjWm/lGenpGsZP0zdTbtSCozfJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA6ffM%2FbtsMcpmzjWm%2FlGenpGsZP0zdTbtSCozfJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;555&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Flutter의 StatelessWidget이나 StatefulWidget과 비슷한 개념입니다. Jetpack Compose에서 UI 컴포넌트를 정의할 때 사용하는 어노테이션입니다. Flutter에서 build() 메서드로 위젯을 만드는 것처럼, Jetpack Compose에서는 @Composable 어노테이션으로 UI 컴포넌트를 만듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;Modifier&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cajaeA/btsMcs4EaLh/VP3Xn4nO80rdaWM0jVQ9N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cajaeA/btsMcs4EaLh/VP3Xn4nO80rdaWM0jVQ9N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cajaeA/btsMcs4EaLh/VP3Xn4nO80rdaWM0jVQ9N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcajaeA%2FbtsMcs4EaLh%2FVP3Xn4nO80rdaWM0jVQ9N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;292&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Flutter의&amp;nbsp;decoration,&amp;nbsp;padding&amp;nbsp;등을&amp;nbsp;통합한&amp;nbsp;개념입니다.&amp;nbsp;Jetpack&amp;nbsp;Compose에서&amp;nbsp;UI&amp;nbsp;컴포넌트의&amp;nbsp;스타일,&amp;nbsp;레이아웃,&amp;nbsp;상호작용을&amp;nbsp;정의하는&amp;nbsp;데&amp;nbsp;사용됩니다.&amp;nbsp;Flutter의&amp;nbsp;Container나&amp;nbsp;여러&amp;nbsp;위젯&amp;nbsp;프로퍼티를&amp;nbsp;한&amp;nbsp;곳에서&amp;nbsp;처리하는&amp;nbsp;느낌입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;* OptIn&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JUTeD/btsMcAaofg8/ZUPzH4FoFubkiyBJsENV1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JUTeD/btsMcAaofg8/ZUPzH4FoFubkiyBJsENV1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JUTeD/btsMcAaofg8/ZUPzH4FoFubkiyBJsENV1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJUTeD%2FbtsMcAaofg8%2FZUPzH4FoFubkiyBJsENV1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;379&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- OptIn는 간단하게 설명하자면 Jetpack Compose에서 실험적인 API를 사용할 때 사용하는 어노테이션입니다. 이 어노테이션은 Flutter의 @experimental 주석이나 프리뷰 기능과 매우 유사합니다. 해당 어노테이션을 사용하면 최신 UI 컴포넌트를 시도해볼 수 있습니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;* 해당 어노테이션을 사용해서 적용한 기능은 향후 변경되거나 완전히 제거될 수 있으니, 프로덕션에서는 사용을 자제해야합니다.&lt;/span&gt; 저는 개인 프로젝트이고 최대한 새로운 기술을 써보고 싶어서 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;MutableStateFlow&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1KQXt/btsMcfq0OMA/ztFsI7AhCFMqTVxMedKvx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1KQXt/btsMcfq0OMA/ztFsI7AhCFMqTVxMedKvx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1KQXt/btsMcfq0OMA/ztFsI7AhCFMqTVxMedKvx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1KQXt%2FbtsMcfq0OMA%2FztFsI7AhCFMqTVxMedKvx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;256&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Flutter의&amp;nbsp;ValueNotifier나&amp;nbsp;ChangeNotifier와&amp;nbsp;비슷한&amp;nbsp;반응형&amp;nbsp;상태&amp;nbsp;관리&amp;nbsp;도구입니다.&amp;nbsp;상태가&amp;nbsp;변경되면&amp;nbsp;자동으로&amp;nbsp;UI를&amp;nbsp;업데이트하는&amp;nbsp;메커니즘을&amp;nbsp;제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;Flow&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7l8Yk/btsMeTlLprH/5Ur1fZosFkwC2r3VqCIAGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7l8Yk/btsMeTlLprH/5Ur1fZosFkwC2r3VqCIAGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7l8Yk/btsMeTlLprH/5Ur1fZosFkwC2r3VqCIAGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7l8Yk%2FbtsMeTlLprH%2F5Ur1fZosFkwC2r3VqCIAGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;579&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- RxDart의&amp;nbsp;Stream과&amp;nbsp;매우&amp;nbsp;유사한&amp;nbsp;비동기&amp;nbsp;데이터&amp;nbsp;스트림입니다.&amp;nbsp;Flutter에서&amp;nbsp;Stream을&amp;nbsp;사용하는&amp;nbsp;것과&amp;nbsp;비슷하게&amp;nbsp;데이터의&amp;nbsp;연속된&amp;nbsp;흐름을&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;invoke&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVWUhw/btsMdJqZCLo/TpakppgChVru0mMwnt9MLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVWUhw/btsMdJqZCLo/TpakppgChVru0mMwnt9MLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVWUhw/btsMdJqZCLo/TpakppgChVru0mMwnt9MLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVWUhw%2FbtsMdJqZCLo%2FTpakppgChVru0mMwnt9MLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;184&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Dart의 call() 메서드와 비슷한 개념입니다. 객체를 함수처럼 호출할 수 있게 해주는 연산자입니다. 추가적인 설명을 드리자면, 객체를 함수처럼 호출할 수 있는 것은 invoke가 약속된 사용법이기 때문입니다. 예를 들어, 컴파일 시점에서 컴파일러가 obj(args)를 obj.invoke(args)로 자동 변환하는데, 이로인해 객체를 함수처럼 호출할 수 있게 해줍니다.&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;Result&amp;lt;out&amp;nbsp;T&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gdl5q/btsMd1Ze9RC/oI013KUcYnXhOmT1HEjukk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gdl5q/btsMd1Ze9RC/oI013KUcYnXhOmT1HEjukk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gdl5q/btsMd1Ze9RC/oI013KUcYnXhOmT1HEjukk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGdl5q%2FbtsMd1Ze9RC%2FoI013KUcYnXhOmT1HEjukk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;321&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Dart의&amp;nbsp;try-catch와&amp;nbsp;비슷하지만&amp;nbsp;더&amp;nbsp;함수형&amp;nbsp;프로그래밍&amp;nbsp;스타일의&amp;nbsp;에러&amp;nbsp;핸들링&amp;nbsp;방식입니다.&amp;nbsp;성공&amp;nbsp;또는&amp;nbsp;실패&amp;nbsp;상태를&amp;nbsp;명시적으로&amp;nbsp;다룰&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;viewModelScope&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;749&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mingv/btsMdIZVn3H/SjCU4uvF5W0cWVkW0dgGY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mingv/btsMdIZVn3H/SjCU4uvF5W0cWVkW0dgGY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mingv/btsMdIZVn3H/SjCU4uvF5W0cWVkW0dgGY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmingv%2FbtsMdIZVn3H%2FSjCU4uvF5W0cWVkW0dgGY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1005&quot; height=&quot;749&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;749&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;-&amp;nbsp;Flutter의 Provider나 GetX에서 사용하는 생명주기 관리와 비슷합니다. ViewModel의 생명주기에 맞춰 코루틴을 관리합니다. Flutter의 setState()와 비슷하다고 생각하시면 됩니다. 그리고 ViewModel의 생명주기에 맞춘다는 이야기는 ViewModel이 소멸될 때 자동으로 모든 코루틴을 취소한다는 뜻입니다. 때문에 메모리 누수를 방지할 수 있고 리소스도 자동으로 정리할 수 있습니다. 무엇보다 비동기 작업을 안전하게 수행할 수 있고, UI 스레드를 블로킹하지 않고 백그라운드에서 작업을 수행하기 때문에 화면이 버벅거리거나 멈추지도 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;* &lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;coroutines(코루틴): &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;lauch &amp;amp; async &amp;amp; withContext &amp;amp; coroutineScope &amp;amp; repeat&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- 위의 viewModelScope에서 주로 사용되는 메서드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;launch&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232546238&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModelScope.launch {
    // 백그라운드 작업 수행
    repository.fetchData()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;결과를 반환하지 않는 비동기 작업에 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;async&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232560705&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModelScope.launch {
    val result1 = async { repository.fetchFirstData() }
    val result2 = async { repository.fetchSecondData() }
    
    // 두 작업의 결과를 동시에 기다림
    val combinedResult = result1.await() to result2.await()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;결과를 반환하는 비동기 작업에 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;await()로 결과를 기다릴 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;주로 병렬 처리가 필요할 때 사용합니다. (순서 제어)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;withContext&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232637512&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModelScope.launch {
    val result = withContext(Dispatchers.IO) {
        // 백그라운드 스레드에서 작업 수행
        repository.heavyComputation()
    }
    // 메인 스레드로 돌아와 UI 업데이트
    _uiState.value = result
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코루틴의 컨텍스트를 변경할 때 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;주로 스레드 변경(디스패처 변경)에 활용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;coroutineScope&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232705313&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModelScope.launch {
    coroutineScope {
        launch { task1() }
        launch { task2() }
        // 두 작업 모두 완료될 때까지 대기
    }
    // 여기서 모든 자식 코루틴 작업 완료
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;자식 코루틴들의 완료를 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;모든 자식 코루틴이 완료될 때까지 대기합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;repeat&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232739239&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModelScope.launch {
    repeat(5) { index -&amp;gt;
        launch {
            // 5번 비동기적으로 작업 수행
            fetchDataForIndex(index)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;반복 작업을 위해서 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;비동기 반복 작업에 유용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;CoroutineScope&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- 비동기 작업을 관리하는 범위를 정의합니다. Flutter의 Future와 유사하지만 더 경량화되고 효율적인 비동기 처리 메커니즘입니다. 위에 적은 'coroutineScope'와는 다릅니다. 좀 더 추가적으로 설명해드릴게요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt; coroutineScope &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232883125&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun fetchMultipleData() = coroutineScope {
    val data1 = async { fetchFirstData() }
    val data2 = async { fetchSecondData() }
    
    // 두 작업 모두 완료될 때까지 대기
    data1.await() to data2.await()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;함수 내부에서 여러 자식 코루틴을 생성하고 모든 자식 코루틴의 완료를 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;비동기적으로 작업을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;부모 코루틴이 자식 코루틴들의 실패를 전파 받습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;중단 함수(suspend function)에서 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;CoroutineScope&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739232897476&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
    // 작업 수행
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코루틴을 생성하고 관리하는 인터페이스입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코루틴의 생명주기를 제어하는 범위(scope) 정의합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;직접 생성하거나 특정 디스패처와 함께 사용 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;* runBlocking&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739233021162&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    // 메인 스레드를 블로킹하며 코루틴 실행
    val result = async { fetchData() }
    println(result.await())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- 코루틴을 블로킹 방식으로 실행합니다. 주로 테스트나 특정 상황에서 사용되며, Flutter의 동기 Future 실행과 비슷한 개념입니다. 현재 스레드를 블로킹하면서 코루틴을 실행합니다. 주로 suspend가 아닌 일반 함수에서 코루틴 코드를 실행할 때 사용합니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;* 메인 스레드에서 사용하면 UI를 블로킹할 수 있습니다.&lt;/span&gt; 그렇기 때문에 주로 테스트나 특수한 상황에서 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;아무래도 비슷한 설명이 많다보니 알듯말듯 헷갈리실 것 같아서 좀 더 쉽게 설명해드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;coroutineScope: 비동기적, 중단 함수(suspend function)내에서 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CoroutineScope: 코루틴 범위를 생성 및 관리합니다. (아래에 Dispatchers 설명을 보시면 이해하는데 도움이 되실 겁니다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;runBlocking:&amp;nbsp;동기적,&amp;nbsp;스레드&amp;nbsp;블로킹&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Flutter 관점에서 보면 이렇게도 설명할 수 있습니다. (절대 똑같다는 말이 아닙니다. 느낌이 비슷한겁니다~!)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;coroutineScope = Future.wait()&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CoroutineScope = Stream 컨트롤러&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;runBlocking = 동기 Future 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* &lt;b&gt;Dispatchers &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Kotlin의 Dispatchers는 코루틴이 실행될 스레드를 결정하는 컨텍스트입니다. 주요 Dispatchers는 Main &amp;amp; IO &amp;amp; Default &amp;amp; Unconfined가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Main&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739233506707&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withContext(Dispatchers.Main) {
    // UI 업데이트 작업
    textView.text = &quot;Updated&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;UI 스레드에서 실행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;UI 업데이트나 이벤트 처리에 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;IO&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739233542785&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withContext(Dispatchers.IO) {
    // 네트워크 호출, 파일 읽기 등
    val result = apiService.fetchData()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;네트워크, 파일 I/O 작업에 최적화 되어있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;백그라운드 스레드 풀을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;블로킹&amp;nbsp;I/O&amp;nbsp;작업을&amp;nbsp;위한&amp;nbsp;디스패처&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* 스레드 풀(Thread Pool)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739233772868&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DataRepository {
    private val backgroundDispatcher = Dispatchers.IO.limitedParallelism(4)

    suspend fun fetchMultipleData() = withContext(backgroundDispatcher) {
        // 최대 4개의 병렬 작업 처리
        coroutineScope {
            val job1 = async { fetchFirstData() }
            val job2 = async { fetchSecondData() }
            val job3 = async { fetchThirdData() }

            // 모든 작업 동시 실행
            listOf(job1, job2, job3).awaitAll()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- 백그라운드 스레드 풀(Background Thread Pool)은 동시에 여러 작업을 비동기적으로 실행할 수 있도록 미리 생성되어 대기하고 있는 스레드들의 집합입니다. 특징으로는 스레드 재사용이 있습니다. 매번 새로운 스레드를 생성하는 대신 미리 생성된 스레드를 재사용합니다. 그렇기에 스레드 생성 및 소멸의 오버헤드가 감소합니다. 다만.. 단점이 정말 많습니다. 리소스 제한 &amp;amp; 오버헤드 &amp;amp; 데드락 &amp;amp; 예측 불가능한 실행 순서 &amp;amp; 디버깅 복잡 &amp;amp; 튜닝 어려움 등등 단점이 정말 많기 때문에 잘 쓰지는 않습니다. (코루틴을 잘 활용합시다!)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Default&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739233913597&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withContext(Dispatchers.Default) {
    // 대규모 데이터 처리, 복잡한 계산
    val processedList = largeList.map { complexComputation(it) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CPU 집약적인 작업에 최적화 되어있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기본 스레드 풀을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;복잡한 계산, 데이터 처리에 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* 이해를 돕기 위해 CPU 집약적인 작업과 기본 스레드 풀의 장단점을 설명해드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* &lt;span style=&quot;color: #8a3db6;&quot;&gt;[ CPU 집약적인 작업 ]&lt;/span&gt; - &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;복잡한 계산을 효율적으로 처리할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CPU 코어 활용도가 최대화됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;병렬 처리를 통한 성능이 향상됩니다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;시스템 리소스를 효율적으로 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;*&lt;span style=&quot;color: #8a3db6;&quot;&gt; [ CPU 집약적인 작업 ]&lt;/span&gt; - &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;과도한 CPU 사용으로 인한 시스템 부하가 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다른 애플리케이션 성능 저하 가능성이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;배터리 소모가 증가합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;잘못된 최적화로 인한 성능 저하의 위험이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[ 기본 스레드 풀 ]&lt;/span&gt; - &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스레드 재사용으로 생성/삭제 오버헤드가 감소합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;시스템 리소스를 효율적으로 관리할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;동적인 작업 처리가 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;일관된 성능을 유지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;[ 기본 스레드 풀 ]&lt;/span&gt; - &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;고정된 스레드 풀 크기로 인한 유연성이 제한됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;부하에 따른 대기 시간이 증가합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스레드 풀 크기 튜닝이 복잡합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;잘못된 스레드 풀 설정으로 인해 성능이 저하될 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Unconfined&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739234009864&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withContext(Dispatchers.Unconfined) {
    // 스레드 이동이 자유로운 작업
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;특별한 경우에만 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;호출한 스레드에서 시작, 중단 지점에서 다른 스레드로 이동 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* 주의해서 사용해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* &lt;span style=&quot;color: #409d00;&quot;&gt;[ 호출한 스레드에서 시작, 중단 지점에서 다른 스레드로 이동 가능 ]&lt;/span&gt; -&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; 장점&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;동적인 스레드 전환으로 유연성이 높습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;블로킹 없는 비동기 처리가 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;리소스를 효율적으로 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;복잡한 비동기 워크플로우 구현에 용이합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;* &lt;span style=&quot;color: #409d00;&quot;&gt;[&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;호출한 스레드에서 시작, 중단 지점에서 다른 스레드로 이동 가능 ]&lt;/span&gt; - &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스레드 동작이 예측 불가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;디버깅이 어렵습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;잠재적인 레이스 컨디션이 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;컨텍스트 스위칭 오버헤드가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* 레이스 컨디션&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739234529931&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Counter {
    private var count = 0
    
    // 레이스 컨디션 발생 가능한 메서드
    fun increment() {
        // 읽기 -&amp;gt; 계산 -&amp;gt; 쓰기 과정에서 동시성 문제 발생
        count++
    }
}

// 동시에 여러 스레드에서 호출
fun demonstrateRaceCondition() {
    val counter = Counter()
    repeat(1000) {
        Thread { counter.increment() }.start()
    }
    // 예상 결과: 1000
    // 실제 결과: 1000보다 작을 수 있음
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;여러 스레드나 프로세스가 동시에 같은 자원에 접근하려 할 때 발생하는 경쟁 상태입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실행 순서에 따라 결과가 달라질 수 있는 비결정적 상황입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* 컨텍스트 스위칭 오버헤드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739234590712&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 빈번한 컨텍스트 스위칭
fun frequentContextSwitch() {
    repeat(1000) {
        Thread {
            // 매우 짧은 작업
            println(&quot;Short task&quot;)
        }.start()
    }
}

// 개선된 접근
fun efficientConcurrency() {
    val executor = Executors.newFixedThreadPool(4)
    repeat(1000) {
        executor.submit {
            // 스레드 풀로 컨텍스트 스위칭 최소화
            println(&quot;Efficient task&quot;)
        }
    }
}

// 코루틴은 경량 스레드로 컨텍스트 스위칭 비용 최소화
suspend fun efficientCoroutines() = coroutineScope {
    repeat(1000) {
        launch {
            // 매우 가벼운 컨텍스트 전환
            println(&quot;Coroutine task&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하나의 스레드에서 다른 스레드로 제어권을 넘기는 과정에서 발생하는 성능 비용입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CPU가 레지스터 상태, 메모리 매핑 등을 저장하고 복원하는 데 드는 시간입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;주요 오버헤드 요소로는 아래 4가지 정도가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;레지스터 상태 저장/복원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;캐시 메모리 무효화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스케줄러 개입&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;메모리 컨텍스트 전환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* &lt;b&gt;컨텍스트를 구분해서 사용해야 하는 주요 이유&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739234780511&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserRepository {
    suspend fun loadUserData() = withContext(Dispatchers.IO) {
        // 네트워크 호출 (I/O 작업)
        val userProfile = apiService.fetchProfile()
        
        withContext(Dispatchers.Default) {
            // 복잡한 데이터 처리 (CPU 집약적 작업)
            processUserData(userProfile)
        }
        
        withContext(Dispatchers.Main) {
            // UI 업데이트 (메인 스레드)
            updateUI(userProfile)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739234788769&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 나쁜 예: 메인 스레드에서 네트워크 호출
fun loadData() {
    // UI 스레드를 블로킹하여 앱 응답 느려짐
    val data = apiService.fetchData() 
}

// 좋은 예: 컨텍스트 분리
suspend fun loadData() = withContext(Dispatchers.IO) {
    // 백그라운드에서 안전하게 네트워크 호출
    val data = apiService.fetchData()
    
    withContext(Dispatchers.Main) {
        // UI 업데이트는 메인 스레드에서
        updateUI(data)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. 성능 최적화&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;각 디스패처는 특정 유형의 작업에 최적화되어 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Dispatchers.IO는 I/O 작업을 위해 스레드 풀을 효율적으로 관리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Dispatchers.Default는 CPU 집약적 작업을 위해 코어 수에 맞게 스레드를 조정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. UI 응답성 유지&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Dispatchers.Main은 UI 스레드를 블로킹하지 않고 가볍게 유지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;무거운 작업을 다른 디스패처로 이동시켜 앱의 반응성을 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. 리소스 관리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;각 디스패처는 다른 스레드 풀과 스케줄링 전략을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;작업 유형에 따라 시스템 리소스를 효율적으로 할당합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. 교착 상태 방지&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;적절한 컨텍스트 사용으로 스레드 간 블로킹 위험을 최소화합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;각 작업을 적절한 스레드에서 처리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;5. 확장성&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;서로 다른 유형의 작업을 병렬로 처리가 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;작업 유형에 따라 최적의 실행 환경을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* 주의할 점&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;과도한 컨텍스트 스위칭은 오버헤드가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상황에 맞는 적절한 디스패처 선택이 중요합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;*&amp;nbsp;sealed&amp;nbsp;class&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y57Hf/btsMd4PcptU/QBhTnRxo0AK9hcuDcdCm3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y57Hf/btsMd4PcptU/QBhTnRxo0AK9hcuDcdCm3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y57Hf/btsMd4PcptU/QBhTnRxo0AK9hcuDcdCm3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY57Hf%2FbtsMd4PcptU%2FQBhTnRxo0AK9hcuDcdCm3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;529&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Dart의&amp;nbsp;enum과&amp;nbsp;비슷하지만&amp;nbsp;더&amp;nbsp;강력한&amp;nbsp;패턴&amp;nbsp;매칭&amp;nbsp;기능을&amp;nbsp;제공합니다.&amp;nbsp;제한된&amp;nbsp;클래스&amp;nbsp;계층을&amp;nbsp;정의할&amp;nbsp;때&amp;nbsp;사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;'enum'과 'sealed class'에 대해서 좀 더 자세히 설명드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* enum&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235083354&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum class Result { SUCCESS, ERROR, LOADING }

fun handleResult(result: Result) = when(result) {
    Result.SUCCESS -&amp;gt; println(&quot;성공&quot;)
    Result.ERROR -&amp;gt; println(&quot;오류&quot;)
    Result.LOADING -&amp;gt; println(&quot;로딩 중&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;고정된 상수의 집합입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상태 표현에 제한적입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;메서드를 추가할 수는 있지만 제한적입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;단순한 열거형 값을 표현합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* sealed class&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235150739&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

fun handleResult(result: Result) = when(result) {
    is Result.Success -&amp;gt; println(result.data)
    is Result.Error -&amp;gt; println(result.message)
    Result.Loading -&amp;gt; println(&quot;로딩 중&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;복잡한 상태 및 계층 구조의 표현이 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다양한 속성과 메서드를 추가할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;패턴 매칭에 더 유연합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;추상 클래스처럼 동작 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;* let &amp;amp; also &amp;amp; run &amp;amp; apply &amp;amp; with&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- Kotlin에서는 범위 지정 함수를 지원합니다. 이를 활용하면 개발 속도와 편의성을 챙길 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* let (Null 체크, 일회성 작업)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235283954&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val nullableValue: String? = &quot;Hello&quot;
nullableValue?.let {
    println(it.length) // null이 아닐 때만 실행
}

# 또는

nullableValue?.let { custom =&amp;gt;
    println(custom.length) // null이 아닐 때만 실행
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Null 체크와 함께 사용합니다. (* 주로 null 체크 목적으로 아주 많이 씁니다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;블록 내부에서 it 키워드로 객체를 참조합니다. (it 키워드는 변경할 수 있습니다. 위의 예시에서는 'custom')&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* also (부수 작업, 원본 객체 유지)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235402322&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = mutableListOf(1, 2, 3)
numbers.also {
    it.add(4)
    println(&quot;List modified: $it&quot;)
}
// 원래 numbers 리스트 반환&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;객체에 대해 추가 작업을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;원래 객체를 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* run (복잡한 초기화, 계산)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235459077&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = &quot;Test&quot;.run {
    length // 블록의 마지막 값 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;객체의 새로운 컨텍스트에서 작업을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;블록의 마지막 표현식을 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* apply (객체 설정)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235501191&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val person = Person().apply {
    name = &quot;John&quot;
    age = 30
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;객체 설정에 주로 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;객체 자체를 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;* with (여러 메서드 연속 호출)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739235534982&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = mutableListOf(1, 2, 3)
with(numbers) {
    add(4)
    remove(2)
    println(this) // numbers 리스트 출력
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;객체의 여러 메서드 연속 호출 시 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;수신 객체를 묵시적으로 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위의 지식을 깊게는 아니어도 키워드만 알고 계셔도 Kotlin 개발에는 문제가 없을 겁니다. 정말 자주 쓰이는 핵심만 다뤘습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그럼, 오늘 포스트는 여기서 마치겠습니다!&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <category>composable</category>
      <category>Compose</category>
      <category>Flow</category>
      <category>flutter</category>
      <category>Invoke</category>
      <category>kotlin</category>
      <category>modifier</category>
      <category>mutablestateflow</category>
      <category>RESULT</category>
      <category>viewmodelscope</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/551</guid>
      <comments>https://kwonputer.tistory.com/551#entry551comment</comments>
      <pubDate>Tue, 11 Feb 2025 10:02:47 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 프로젝트 전체적인 구조</title>
      <link>https://kwonputer.tistory.com/550</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739230105094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rsbZH/hyYfXbFt57/iJDqGecXHNoCprQq82Khd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Buwcf/hyYcgKLw99/xjWxyc2NyWV6i2Cj9OQx4k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rsbZH/hyYfXbFt57/iJDqGecXHNoCprQq82Khd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Buwcf/hyYcgKLw99/xjWxyc2NyWV6i2Cj9OQx4k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서 다룰 내용은 '초성마켓' 프로젝트의 전체적인 구조에 대해서 다룰까 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 해당 프로젝트는 android stuido, kotlin, 라이브러리 등 모두 최신 버전을 사용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;202&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OpT3Q/btsMcTt3hJ6/T9lfZep05QQrCPa15FHBl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OpT3Q/btsMcTt3hJ6/T9lfZep05QQrCPa15FHBl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OpT3Q/btsMcTt3hJ6/T9lfZep05QQrCPa15FHBl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOpT3Q%2FbtsMcTt3hJ6%2FT9lfZep05QQrCPa15FHBl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;202&quot; height=&quot;190&quot; data-origin-width=&quot;202&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처를 차용했으며, data &amp;amp; domain &amp;amp; presenter 총 3개의 레이어가 핵심입니다. common은 공용 유틸이나 type을 다루고 있으며, di는 의존성 역전에 관한 코드를 다루고 있습니다. 참고로 이번 프로젝트에서는 koin을 사용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;263&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ss44W/btsMd8xaNHu/bSl5NnFC4oSKxBdmevkruK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ss44W/btsMd8xaNHu/bSl5NnFC4oSKxBdmevkruK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ss44W/btsMd8xaNHu/bSl5NnFC4oSKxBdmevkruK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fss44W%2FbtsMd8xaNHu%2FbSl5NnFC4oSKxBdmevkruK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;263&quot; height=&quot;189&quot; data-origin-width=&quot;263&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oP2Rr/btsMedZpxdo/J7h1Y91LfBWB1EzBMOnK7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oP2Rr/btsMedZpxdo/J7h1Y91LfBWB1EzBMOnK7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oP2Rr/btsMedZpxdo/J7h1Y91LfBWB1EzBMOnK7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoP2Rr%2FbtsMedZpxdo%2FJ7h1Y91LfBWB1EzBMOnK7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;593&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 백엔드를 따로 구축하지 않고, Firebase의 Firestore만을 사용해서 DB를 구현하고 이용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- quizList: 퀴즈 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- userList: 유저 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- userList&amp;gt;{userId}&amp;gt;quizGroupList: 퀴즈 그룹 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- userList&amp;gt;{userId}&amp;gt;quizResultList: 퀴즈 결과 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 DB 구조는 위와 같이 구성했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OZ3X8/btsMd6MSODK/wfqgzXHx6QnoNdvaQ5oeFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OZ3X8/btsMd6MSODK/wfqgzXHx6QnoNdvaQ5oeFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OZ3X8/btsMd6MSODK/wfqgzXHx6QnoNdvaQ5oeFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOZ3X8%2FbtsMd6MSODK%2FwfqgzXHx6QnoNdvaQ5oeFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;257&quot; height=&quot;485&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data 레이어는 위와 같이 구성되어있습니다. db 패키지에는 Firestore와 연결하기 위한 클래스와 내부 DB가 있으며, mapper 패키지에는 클라이언트의 model과 Firestore의 data를 상호 변환하기 위한 코드를 다루고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;repository 패키지는 domain 레이어에서 정의한 인터페이스를 구현하는 구현체 코드가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTWcOl/btsMdqdZXN0/Kr8mRnDTrfMMtDJdFhhVDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTWcOl/btsMdqdZXN0/Kr8mRnDTrfMMtDJdFhhVDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTWcOl/btsMdqdZXN0/Kr8mRnDTrfMMtDJdFhhVDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTWcOl%2FbtsMdqdZXN0%2FKr8mRnDTrfMMtDJdFhhVDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;269&quot; height=&quot;651&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;domain 레이어는 위와 같이 구성되어있습니다. model 패키지는 정의한 인터페이스에서 필요한 data class를 다루고 있습니다. repository 패키지에는 프로젝트의 시나리오에 필요한 인터페이스를 정의한 코드가 담겨있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;usecase 패키지에는 시나리오 &amp;amp; 비즈니스 로직이 담긴 코드를 다루고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;241&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlMbjs/btsMd1LJwz2/SDbrjDUq8H63x1GfMxSo9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlMbjs/btsMd1LJwz2/SDbrjDUq8H63x1GfMxSo9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlMbjs/btsMd1LJwz2/SDbrjDUq8H63x1GfMxSo9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlMbjs%2FbtsMd1LJwz2%2FSDbrjDUq8H63x1GfMxSo9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;241&quot; height=&quot;527&quot; data-origin-width=&quot;241&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;presenter 레이어는 위와 같이 구성되어있습니다. page 패키지에는 시나리오에 따른 페이지 UI와 ViewModel 코드가 담겨있습니다. widget 패키지에는 프로젝트 UI에서 공통적으로 사용할 위젯이 담겨져있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;route 패키지에는 라우터 코드가 있는데, 해당 프로젝트에서는 라우트를 한 곳에서 집중 관리하기 위해 따로 만들어서 사용하고 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트는 현재까지 개발한 프로젝트 구조를 간단하게 설명한 글입니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트는, 제가 몇 개월만에 다시 Kotlin으로 개발하고, 또 Compose 등 새로운 지식을 습득하면서 검색하고 공부했던 지식들을 간단하게 글로 작성해보겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/550</guid>
      <comments>https://kwonputer.tistory.com/550#entry550comment</comments>
      <pubDate>Tue, 11 Feb 2025 08:20:25 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 홈, 퀴즈 페이지 개발</title>
      <link>https://kwonputer.tistory.com/549</link>
      <description>&lt;p style=&quot;color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739219182767&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rsbZH/hyYfXbFt57/iJDqGecXHNoCprQq82Khd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Buwcf/hyYcgKLw99/xjWxyc2NyWV6i2Cj9OQx4k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rsbZH/hyYfXbFt57/iJDqGecXHNoCprQq82Khd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Buwcf/hyYcgKLw99/xjWxyc2NyWV6i2Cj9OQx4k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후.. 생각보다 시간이 더 걸렸네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 제 상태가 너무 피로해서.. 이번 포스트는 동영상으로 대체하고 주석 작업을 해서 GitHub에 공유드리겠습니다. 참고 부탁드립니다~!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA2Wi2/btsMesvgZtW/ngeP7cHcFVVlVHcuRfqmS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA2Wi2/btsMesvgZtW/ngeP7cHcFVVlVHcuRfqmS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA2Wi2/btsMesvgZtW/ngeP7cHcFVVlVHcuRfqmS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA2Wi2%2FbtsMesvgZtW%2FngeP7cHcFVVlVHcuRfqmS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1263&quot; height=&quot;805&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxAti/btsMcpz4u4Q/zQYzAMpNHLJ9JwvozEo5e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxAti/btsMcpz4u4Q/zQYzAMpNHLJ9JwvozEo5e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxAti/btsMcpz4u4Q/zQYzAMpNHLJ9JwvozEo5e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxAti%2FbtsMcpz4u4Q%2FzQYzAMpNHLJ9JwvozEo5e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1275&quot; height=&quot;805&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;807&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mt23Z/btsMd9JDXob/LN1hSWPawJeQvUoZcEB0B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mt23Z/btsMd9JDXob/LN1hSWPawJeQvUoZcEB0B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mt23Z/btsMd9JDXob/LN1hSWPawJeQvUoZcEB0B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmt23Z%2FbtsMd9JDXob%2FLN1hSWPawJeQvUoZcEB0B1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1276&quot; height=&quot;807&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;807&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGQqhn/btsMcKxgbDD/ZnM40NUZYz0mBOXQxuJjS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGQqhn/btsMcKxgbDD/ZnM40NUZYz0mBOXQxuJjS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGQqhn/btsMcKxgbDD/ZnM40NUZYz0mBOXQxuJjS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGQqhn%2FbtsMcKxgbDD%2FZnM40NUZYz0mBOXQxuJjS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1271&quot; height=&quot;805&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;801&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzi844/btsMcSBQltk/8ya1egVzWfZsr3G4o5v18k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzi844/btsMcSBQltk/8ya1egVzWfZsr3G4o5v18k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzi844/btsMcSBQltk/8ya1egVzWfZsr3G4o5v18k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzi844%2FbtsMcSBQltk%2F8ya1egVzWfZsr3G4o5v18k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1270&quot; height=&quot;801&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;801&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/549</guid>
      <comments>https://kwonputer.tistory.com/549#entry549comment</comments>
      <pubDate>Mon, 10 Feb 2025 22:20:21 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 로그인 페이지 개발</title>
      <link>https://kwonputer.tistory.com/548</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738916032197&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wigJ2/hyYb6geF82/qR5EX9qi2Wk5bVUpADGhGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wigJ2/hyYb6geF82/qR5EX9qi2Wk5bVUpADGhGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 로그인 페이지 개발에 대한 포스트를 작성해보려고 합니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 제가 사용할 이미지는 아래 2개입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;초성초성_배경.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIIQS/btsMaNUBbmD/cR0ZniyAmbQowrOv6wbxuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIIQS/btsMaNUBbmD/cR0ZniyAmbQowrOv6wbxuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIIQS/btsMaNUBbmD/cR0ZniyAmbQowrOv6wbxuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIIQS%2FbtsMaNUBbmD%2FcR0ZniyAmbQowrOv6wbxuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;초성초성_배경.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;초성초성_배경제거.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b62tlK/btsL91MK87y/5KUgcDUo45Zww3X3fUiRDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b62tlK/btsL91MK87y/5KUgcDUo45Zww3X3fUiRDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b62tlK/btsL91MK87y/5KUgcDUo45Zww3X3fUiRDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb62tlK%2FbtsL91MK87y%2F5KUgcDUo45Zww3X3fUiRDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;초성초성_배경제거.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지를 활용하기 전에, 아래 사이트에 접속해서 이미지를 디바이스 해상도별로 나누겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.appicon.co/#image-sets&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.appicon.co/#image-sets&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738908354426&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;App Icon Generator&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.appicon.co&quot; data-og-source-url=&quot;https://www.appicon.co/#image-sets&quot; data-og-url=&quot;https://www.appicon.co/#image-sets&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.appicon.co/#image-sets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.appicon.co/#image-sets&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;App Icon Generator&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.appicon.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eM6RxL/btsMbGAj24Y/N8TE1Bd0H8ZQGy2ORvK361/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eM6RxL/btsMbGAj24Y/N8TE1Bd0H8ZQGy2ORvK361/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eM6RxL/btsMbGAj24Y/N8TE1Bd0H8ZQGy2ORvK361/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeM6RxL%2FbtsMbGAj24Y%2FN8TE1Bd0H8ZQGy2ORvK361%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1023&quot; height=&quot;561&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음, 카카오 디벨로퍼로 넘어오셔서 설정값들을 바꿔줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1309&quot; data-origin-height=&quot;1045&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GIm1Z/btsMbxKwnrJ/M86qtyk2BV7kOBJKHHLsn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIm1Z/btsMbxKwnrJ/M86qtyk2BV7kOBJKHHLsn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIm1Z/btsMbxKwnrJ/M86qtyk2BV7kOBJKHHLsn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIm1Z%2FbtsMbxKwnrJ%2FM86qtyk2BV7kOBJKHHLsn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1309&quot; height=&quot;1045&quot; data-origin-width=&quot;1309&quot; data-origin-height=&quot;1045&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음! LoginPage를 완성시켜 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MTq6z/btsMavtkSai/4aGNSOWfpR5vtkgcZKhTR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MTq6z/btsMavtkSai/4aGNSOWfpR5vtkgcZKhTR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MTq6z/btsMavtkSai/4aGNSOWfpR5vtkgcZKhTR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMTq6z%2FbtsMavtkSai%2F4aGNSOWfpR5vtkgcZKhTR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;1315&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;1258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b126wv/btsMcfCxtDZ/dQ2Q4W1fM3zJWbK79fNvJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b126wv/btsMcfCxtDZ/dQ2Q4W1fM3zJWbK79fNvJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b126wv/btsMcfCxtDZ/dQ2Q4W1fM3zJWbK79fNvJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb126wv%2FbtsMcfCxtDZ%2FdQ2Q4W1fM3zJWbK79fNvJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;1258&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;1258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;1301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/casbvU/btsMaShrAzC/xVTrga8kSjxCB5sGaTKQ40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/casbvU/btsMaShrAzC/xVTrga8kSjxCB5sGaTKQ40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/casbvU/btsMaShrAzC/xVTrga8kSjxCB5sGaTKQ40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcasbvU%2FbtsMaShrAzC%2FxVTrga8kSjxCB5sGaTKQ40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;865&quot; height=&quot;1301&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;1301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainActivity도 로그인 화면 테스트를 위해 수정해줍시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qywee/btsMcb7ZpJp/NkCyyScQQ92gAtNfcmvHa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qywee/btsMcb7ZpJp/NkCyyScQQ92gAtNfcmvHa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qywee/btsMcb7ZpJp/NkCyyScQQ92gAtNfcmvHa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqywee%2FbtsMcb7ZpJp%2FNkCyyScQQ92gAtNfcmvHa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;928&quot; height=&quot;1129&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1037&quot; data-origin-height=&quot;1320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mVTAb/btsMaNggdLA/i9KV3nikjntysQUzfFR4LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mVTAb/btsMaNggdLA/i9KV3nikjntysQUzfFR4LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mVTAb/btsMaNggdLA/i9KV3nikjntysQUzfFR4LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmVTAb%2FbtsMaNggdLA%2Fi9KV3nikjntysQUzfFR4LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1037&quot; height=&quot;1320&quot; data-origin-width=&quot;1037&quot; data-origin-height=&quot;1320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 UserRepository의 signInWithKakao 함수에서 id를 찾는 부분이 있는데, 이 부분은 firestore의 문서 ID를 활용할 생각입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Choyo/btsMbJKTS1a/b8SvilobrvCVAeqFpkNz5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Choyo/btsMbJKTS1a/b8SvilobrvCVAeqFpkNz5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Choyo/btsMbJKTS1a/b8SvilobrvCVAeqFpkNz5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FChoyo%2FbtsMbJKTS1a%2Fb8SvilobrvCVAeqFpkNz5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;970&quot; height=&quot;1324&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;1324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래처럼 &quot;id&quot;값을 매핑해줍시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNhOzD/btsL95IxdEi/XvuTfflenzz64ddbQVHDR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNhOzD/btsL95IxdEi/XvuTfflenzz64ddbQVHDR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNhOzD/btsL95IxdEi/XvuTfflenzz64ddbQVHDR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNhOzD%2FbtsL95IxdEi%2FXvuTfflenzz64ddbQVHDR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;971&quot; height=&quot;1226&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKbpOI/btsMb2DmbUq/KotThhyWmc00tIQ7ksj7MK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKbpOI/btsMb2DmbUq/KotThhyWmc00tIQ7ksj7MK/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250207_165552238.jpg&quot; width=&quot;300&quot; height=&quot;769&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKbpOI/btsMb2DmbUq/KotThhyWmc00tIQ7ksj7MK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKbpOI%2FbtsMb2DmbUq%2FKotThhyWmc00tIQ7ksj7MK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9eP4W/btsMaVkYg1Y/CiXEy1d3wOd9iaFKE3fCF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9eP4W/btsMaVkYg1Y/CiXEy1d3wOd9iaFKE3fCF0/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot; data-filename=&quot;KakaoTalk_20250207_165552238_01.jpg&quot; width=&quot;300&quot; height=&quot;769&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9eP4W/btsMaVkYg1Y/CiXEy1d3wOd9iaFKE3fCF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9eP4W%2FbtsMaVkYg1Y%2FCiXEy1d3wOd9iaFKE3fCF0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1485&quot; data-origin-height=&quot;911&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBFDzn/btsMbISIu97/QapCrGTk91GBRZ6aMz9Vi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBFDzn/btsMbISIu97/QapCrGTk91GBRZ6aMz9Vi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBFDzn/btsMbISIu97/QapCrGTk91GBRZ6aMz9Vi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBFDzn%2FbtsMbISIu97%2FQapCrGTk91GBRZ6aMz9Vi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1485&quot; height=&quot;911&quot; data-origin-width=&quot;1485&quot; data-origin-height=&quot;911&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 동작하네요! 디테일은 나중에 잡고 우선 화면을 쭉쭉 밀어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트는 홈 페이지를 작업하는 글을 올릴 것 같습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/548</guid>
      <comments>https://kwonputer.tistory.com/548#entry548comment</comments>
      <pubDate>Fri, 7 Feb 2025 17:10:16 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 클린 아키텍처 적용: DI</title>
      <link>https://kwonputer.tistory.com/547</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738916025760&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wigJ2/hyYb6geF82/qR5EX9qi2Wk5bVUpADGhGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wigJ2/hyYb6geF82/qR5EX9qi2Wk5bVUpADGhGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트는 DI 관련된 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 Kotlin으로 클린 아키텍처를 적용하기 위해서 라이브러리를 살펴봤는데요, 검색 과정에서 제가 모르던 단어도 알게됬습니다. 저는 의존성 역전, 의존성 주입이라는 키워드로 검색을 했는데, 이걸 줄여서 DI(Dependency Injection)이라고 부르더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 라이브러리가 존재하는데 저는 Koin을 채택했습니다. 저번 포스트에 말씀드렸던 것 처럼, 런타임 방식이라서 소규모 프로젝트에 적합할 것 같아서 채택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dagger2는 빌드 방식이고, Koin은 런타임 방식인데, Hilt는 좀 어중간한 포지션이더라구요. (제가 Android DI 관련 라이브러리 지식이 없어서 가볍게 언급하는겁니다~)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼! Koin을 적용해보도록 하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, dependencies에 추가해줍시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;1005&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2t1DK/btsL9M9jIDG/Uw79Nkfg7JZ5JFTiVY48VK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2t1DK/btsL9M9jIDG/Uw79Nkfg7JZ5JFTiVY48VK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2t1DK/btsL9M9jIDG/Uw79Nkfg7JZ5JFTiVY48VK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2t1DK%2FbtsL9M9jIDG%2FUw79Nkfg7JZ5JFTiVY48VK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;1005&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;1005&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음에, libs.versions.toml에 버전을 명시해줍시다! 저는 가장 최신 버전을 사용해보겠습니다~&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;1274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFhrw0/btsL98RIWjh/Gnt9pTYu74cN6o4EEKYDU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFhrw0/btsL98RIWjh/Gnt9pTYu74cN6o4EEKYDU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFhrw0/btsL98RIWjh/Gnt9pTYu74cN6o4EEKYDU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFhrw0%2FbtsL98RIWjh%2FGnt9pTYu74cN6o4EEKYDU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;1274&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;1274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고나서, 새로운 패키지로 di를 만들어줍시다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rc1M9/btsMbqLdiuS/b8cLCbiUaV0F5KJVAoTbwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rc1M9/btsMbqLdiuS/b8cLCbiUaV0F5KJVAoTbwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rc1M9/btsMbqLdiuS/b8cLCbiUaV0F5KJVAoTbwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRc1M9%2FbtsMbqLdiuS%2Fb8cLCbiUaV0F5KJVAoTbwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;268&quot; height=&quot;242&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음에, dataModule은 data 레이어를 엮어주고, domainModule은 domain 레이어를 엮어주면 되겠습니다~!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3qfl1/btsL95BsBx5/7aukZ2RcZmwORQVf1068b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3qfl1/btsL95BsBx5/7aukZ2RcZmwORQVf1068b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3qfl1/btsL95BsBx5/7aukZ2RcZmwORQVf1068b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3qfl1%2FbtsL95BsBx5%2F7aukZ2RcZmwORQVf1068b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;828&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhqw2x/btsMakd6yux/YOtDomDEmBCOOcwEe9Fk00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhqw2x/btsMakd6yux/YOtDomDEmBCOOcwEe9Fk00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhqw2x/btsMakd6yux/YOtDomDEmBCOOcwEe9Fk00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhqw2x%2FbtsMakd6yux%2FYOtDomDEmBCOOcwEe9Fk00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;621&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 클린 아키텍처를 적용하면 이미 의존성 관련된 부분은 대부분 해결이 된 상황이고, 라이브러리를 통해 더 깔끔하게 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;koin 사용 예시를 보고 한번 시도해봤는데요, 대충 아래처럼 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738906556564&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class QuizState {
    object Loading : QuizState()
    data class Success(val quizGroups: List&amp;lt;QuizGroupData&amp;gt;) : QuizState()
    data class Error(val message: String) : QuizState()
    object QuizSubmitted : QuizState()
}

# 1. ViewModel 주입받기
@Composable
fun QuizScreen(
    viewModel: QuizViewModel = koinViewModel()
) {
    // ViewModel 사용
    val quizState by viewModel.quizState.collectAsStateWithLifecycle()
}

# 2. Dependency 직접 주입받기
@Composable
fun UserProfileScreen() {
    // Repository 직접 주입
    val userRepository: UserRepositoryImpl = get()
    
    // UseCase 직접 주입
    val updateProfileUseCase: UpdateProfileUseCase = get()
}

# 3. ViewModel 예제
class QuizViewModel(
    private val quizRepository: QuizRepositoryImpl,
    private val sessionRepository: SessionRepositoryImpl,
    private val processQuizResultUseCase: ProcessQuizResultUseCase
) : ViewModel() {
    
    private val _quizState = MutableStateFlow&amp;lt;QuizState&amp;gt;(QuizState.Loading)
    val quizState = _quizState.asStateFlow()

    init {
        viewModelScope.launch {
            // Repository 사용
            quizRepository.observeQuizGroups()
                .collect { quizGroups -&amp;gt;
                    _quizState.value = QuizState.Success(quizGroups)
                }
        }
    }

    fun submitQuiz(quizGroupId: String, answers: List&amp;lt;String&amp;gt;) {
        viewModelScope.launch {
            try {
                processQuizResultUseCase.invoke(quizGroupId, answers)
                    .onSuccess {
                        _quizState.value = QuizState.QuizSubmitted
                    }
                    .onFailure { error -&amp;gt;
                        _quizState.value = QuizState.Error(error.message ?: &quot;알 수 없는 오류가 발생했습니다.&quot;)
                    }
            } catch (e: Exception) {
                _quizState.value = QuizState.Error(e.message ?: &quot;알 수 없는 오류가 발생했습니다.&quot;)
            }
        }
    }
}

# 4. ViewModel 모듈 설정
val viewModelModule = module {
    viewModel { QuizViewModel(get(), get(), get()) }
    viewModel { UserProfileViewModel(get(), get()) }
    viewModel { HomeViewModel(get(), get(), get()) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처를 적용하기 위해서는 오늘 포스트보다는 이전의 포스트들이 더 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어.. 어느정도 구조는 잡은 것 같습니다. 짧은 시간동안 집중력있게 했더니 너무 지치네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;color, textstyles, theme, router도 어느정도 만들어 뒀으니 이제 화면을 밀고 나가면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저한테는 지금부터가 더 고비에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 정말 디자인에 소질이 없어서.. 핀터레스트를 많이 참고해야할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트부터는 화면에 대한 글을 작성하겠습니다~!&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/547</guid>
      <comments>https://kwonputer.tistory.com/547#entry547comment</comments>
      <pubDate>Fri, 7 Feb 2025 14:39:31 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin Project] 초성마켓 - 클린 아키텍처 적용: Data 레이어 개발</title>
      <link>https://kwonputer.tistory.com/546</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/KwonGeneral/chosungmarket.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738916017860&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KwonGeneral/chosungmarket: 초성마켓&quot; data-og-description=&quot;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; data-og-url=&quot;https://github.com/KwonGeneral/chosungmarket&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wigJ2/hyYb6geF82/qR5EX9qi2Wk5bVUpADGhGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KwonGeneral/chosungmarket.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wigJ2/hyYb6geF82/qR5EX9qi2Wk5bVUpADGhGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KwonGeneral/chosungmarket: 초성마켓&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;초성마켓. Contribute to KwonGeneral/chosungmarket development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 Data 레이어 개발에 대해서 다루겠습니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 차후에 계속해서 추가, 수정, 삭제될 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 db 관련된 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 FirebaseStore를 사용하기로 했기 때문에, 이를 바탕으로 간단하게 CRUD만 구현했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHE0W0/btsMafX7JKp/KWSXzHSidYLkXKI2HurJk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHE0W0/btsMafX7JKp/KWSXzHSidYLkXKI2HurJk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHE0W0/btsMafX7JKp/KWSXzHSidYLkXKI2HurJk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHE0W0%2FbtsMafX7JKp%2FKWSXzHSidYLkXKI2HurJk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;870&quot; height=&quot;1307&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEjVot/btsMagpdHj6/JKjwmbavoUgK1dxfAqkYbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEjVot/btsMagpdHj6/JKjwmbavoUgK1dxfAqkYbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEjVot/btsMagpdHj6/JKjwmbavoUgK1dxfAqkYbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEjVot%2FbtsMagpdHj6%2FJKjwmbavoUgK1dxfAqkYbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;866&quot; height=&quot;1324&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;1324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBa0b/btsL9QdsbWQ/ew12paxuAyVWM4fYUfuqE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBa0b/btsL9QdsbWQ/ew12paxuAyVWM4fYUfuqE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBa0b/btsL9QdsbWQ/ew12paxuAyVWM4fYUfuqE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBa0b%2FbtsL9QdsbWQ%2Few12paxuAyVWM4fYUfuqE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;886&quot; height=&quot;1309&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuKezu/btsL9uuYxRM/aUV5K7xX5R0uKHxyhtkKU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuKezu/btsL9uuYxRM/aUV5K7xX5R0uKHxyhtkKU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuKezu/btsL9uuYxRM/aUV5K7xX5R0uKHxyhtkKU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuKezu%2FbtsL9uuYxRM%2FaUV5K7xX5R0uKHxyhtkKU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;1320&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;1316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntKSG/btsMafX8tFL/mApXMszmwV0H5FCKisumBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntKSG/btsMafX8tFL/mApXMszmwV0H5FCKisumBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntKSG/btsMafX8tFL/mApXMszmwV0H5FCKisumBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntKSG%2FbtsMafX8tFL%2FmApXMszmwV0H5FCKisumBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;866&quot; height=&quot;1316&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;1316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, Firebase와 클라이언트 Model을 연결하기 위해서 Mapper를 만들었습니다~!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;1319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H6H62/btsMbCxPmOU/7yWliYsrky2X1CidcDsgS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H6H62/btsMbCxPmOU/7yWliYsrky2X1CidcDsgS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H6H62/btsMbCxPmOU/7yWliYsrky2X1CidcDsgS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH6H62%2FbtsMbCxPmOU%2F7yWliYsrky2X1CidcDsgS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;1319&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;1319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rPCqR/btsL9s416o7/eworFCQ0skD5FekZ89oPnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rPCqR/btsL9s416o7/eworFCQ0skD5FekZ89oPnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rPCqR/btsL9s416o7/eworFCQ0skD5FekZ89oPnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrPCqR%2FbtsL9s416o7%2FeworFCQ0skD5FekZ89oPnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;868&quot; height=&quot;950&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;1323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q345T/btsL97svdJh/lHjqTLfM3YbRLpq1vcs0k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q345T/btsL97svdJh/lHjqTLfM3YbRLpq1vcs0k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q345T/btsL97svdJh/lHjqTLfM3YbRLpq1vcs0k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq345T%2FbtsL97svdJh%2FlHjqTLfM3YbRLpq1vcs0k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;874&quot; height=&quot;1323&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;1323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, repository 부분입니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xaG1e/btsMbtupibg/Q6xn8AdKCk7PSigsiuo9xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xaG1e/btsMbtupibg/Q6xn8AdKCk7PSigsiuo9xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xaG1e/btsMbtupibg/Q6xn8AdKCk7PSigsiuo9xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxaG1e%2FbtsMbtupibg%2FQ6xn8AdKCk7PSigsiuo9xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;1299&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;1315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SUsDA/btsMaM82MEg/BL5cmKvElPQg4LNsWUOk60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SUsDA/btsMaM82MEg/BL5cmKvElPQg4LNsWUOk60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SUsDA/btsMaM82MEg/BL5cmKvElPQg4LNsWUOk60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSUsDA%2FbtsMaM82MEg%2FBL5cmKvElPQg4LNsWUOk60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;869&quot; height=&quot;1315&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;1315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;1319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zlgzW/btsL91y72WL/oeXLLpLsi7tlam8amyLmx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zlgzW/btsL91y72WL/oeXLLpLsi7tlam8amyLmx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zlgzW/btsL91y72WL/oeXLLpLsi7tlam8amyLmx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzlgzW%2FbtsL91y72WL%2FoeXLLpLsi7tlam8amyLmx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1033&quot; height=&quot;1319&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;1319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;1323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jkroo/btsMa7rAPeP/6BC3BmpMvqnScu4dQKgSCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jkroo/btsMa7rAPeP/6BC3BmpMvqnScu4dQKgSCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jkroo/btsMa7rAPeP/6BC3BmpMvqnScu4dQKgSCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJkroo%2FbtsMa7rAPeP%2F6BC3BmpMvqnScu4dQKgSCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;876&quot; height=&quot;1323&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;1323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;1322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBmcgm/btsMa93VPhb/hq7L5vvCxuS7bkf7CZWgt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBmcgm/btsMa93VPhb/hq7L5vvCxuS7bkf7CZWgt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBmcgm/btsMa93VPhb/hq7L5vvCxuS7bkf7CZWgt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBmcgm%2FbtsMa93VPhb%2Fhq7L5vvCxuS7bkf7CZWgt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;826&quot; height=&quot;1322&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;1322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;1336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZNpdP/btsMaOMydQp/fkh5xMej9AwoK2CgTikOB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZNpdP/btsMaOMydQp/fkh5xMej9AwoK2CgTikOB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZNpdP/btsMaOMydQp/fkh5xMej9AwoK2CgTikOB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZNpdP%2FbtsMaOMydQp%2Ffkh5xMej9AwoK2CgTikOB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;1336&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;1336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후.. 이번 포스트는 여기까지 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 대한 설명은 주석으로 대체하겠습니다! (너무 지치네요!!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 DI 관련된 부분에 대해서 다룰게요.&lt;/p&gt;</description>
      <category>개발/모바일</category>
      <author>권퓨터</author>
      <guid isPermaLink="true">https://kwonputer.tistory.com/546</guid>
      <comments>https://kwonputer.tistory.com/546#entry546comment</comments>
      <pubDate>Fri, 7 Feb 2025 14:27:23 +0900</pubDate>
    </item>
  </channel>
</rss>