<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Coder Island</title>
    <link>https://codingisland.tistory.com/</link>
    <description>코딩섬</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 16:15:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>rud_Island</managingEditor>
    <image>
      <title>Coder Island</title>
      <url>https://tistory1.daumcdn.net/tistory/6524446/attach/33e59ed770c74dcd8ccd97d71942054e</url>
      <link>https://codingisland.tistory.com</link>
    </image>
    <item>
      <title>[AWS] 유니티 게임 서버(NestJS) 무중단 배포</title>
      <link>https://codingisland.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 개인 프로젝트로 진행했던 방치형 게임의 백엔드 시스템을 AWS EC2에 올린 과정을 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AWS 계정 보안: 권한 분리 및 MFA 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 가입했을때 root 계정으로 사용하게 되는데 root 계정은 모든 권한을 가진 '마스터 계정' 이라서 사용자계정을 분리 시켜야합니다.&lt;/p&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;IAM 콘솔 접속:&lt;/b&gt; AWS 상단 검색창에 IAM을 입력하고 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;사용자 생성 시작:&lt;/b&gt; 왼쪽 메뉴에서 사용자 -&amp;gt; 사용자 생성 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;사용자 세부 정보 설정:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,1,0,0&quot;&gt;사용자 이름:&lt;/b&gt; Admin-Dev (본인이 알아보기 쉬운 이름)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,1,1,0&quot;&gt;AWS 관리 콘솔에 대한 사용자 액세스 권한 제공:&lt;/b&gt; 체크&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,1,2,0&quot;&gt;사용자 유형:&lt;/b&gt; IAM 사용자를 생성하고 싶음 선택&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,1,3,0&quot;&gt;비밀번호 설정:&lt;/b&gt; 사용자 지정 비밀번호로 안전하게 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;1506&quot; data-origin-height=&quot;797&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfBJdA/dJMcaaEJ2DO/EDHFP11X0wDzHo9Qduzkdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfBJdA/dJMcaaEJ2DO/EDHFP11X0wDzHo9Qduzkdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfBJdA/dJMcaaEJ2DO/EDHFP11X0wDzHo9Qduzkdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfBJdA%2FdJMcaaEJ2DO%2FEDHFP11X0wDzHo9Qduzkdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;430&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;797&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;6,3,0&quot; data-index-in-node=&quot;0&quot;&gt;권한 설정:&lt;/b&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;권한 정책 검색창에 AdministratorAccess 입력 후 체크 (관리자 권한 부여)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;6,4,0&quot; data-index-in-node=&quot;0&quot;&gt;검토 및 생성:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정을 확인하고 사용자 생성을 완료합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;6,4,1,0,0&quot; data-index-in-node=&quot;0&quot;&gt;중요:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성 직후 나오는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;6,4,1,0,0&quot; data-index-in-node=&quot;14&quot;&gt;로그인 URL, 사용자 이름, 암호&lt;/b&gt;가 담긴 .csv 파일을 다운로드하거나 안전한 곳에 기록해 두세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TubrR/dJMcah4VhgD/1Gk3UvklKgU2iHIHOKyZO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TubrR/dJMcah4VhgD/1Gk3UvklKgU2iHIHOKyZO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TubrR/dJMcah4VhgD/1Gk3UvklKgU2iHIHOKyZO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTubrR%2FdJMcah4VhgD%2F1Gk3UvklKgU2iHIHOKyZO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;339&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;339&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;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;MFA(2단계 인증) 설정 (보안의 핵심)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;비밀번호가 유출되더라도 스마트폰 OTP 없이는 로그인이 불가능하게 만듭니다. Root 계정과 새로 만든 IAM 계정 &lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;8&quot;&gt;둘 다&lt;/b&gt; 설정하는 것이 필수입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;사용자 선택:&lt;/b&gt; IAM 리스트에서 방금 만든 Admin-Dev 사용자를 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;보안 자격 증명 탭:&lt;/b&gt; 중간에 있는 보안 자격 증명 탭으로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;MFA 할당:&lt;/b&gt; MFA 장치 할당 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;장치 선택:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,1,0,0&quot;&gt;장치 이름:&lt;/b&gt; MyPhone&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,1,1,0&quot;&gt;MFA 장치 유형:&lt;/b&gt; 인증 앱 (Google Authenticator 등) 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0&quot;&gt;앱 연동:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9,4,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스마트폰에서 Google Authenticator 앱을 실행합니다.&lt;/li&gt;
&lt;li&gt;QR 코드 표시를 누르고 앱으로 스캔합니다.&lt;/li&gt;
&lt;li&gt;앱에 뜨는 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;9,4,1,2,0&quot;&gt;6자리 번호 2개&lt;/b&gt;를 차례대로 입력(코드 1, 코드 2)하고 MFA 추가를 누릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;Root 계정 보안 조치 (마무리)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;이제 Root 계정은 더 이상 쓰지 않도록 잠궈야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;Root 계정 로그아웃&lt;/b&gt; 후, 아까 저장한 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;12,0,0&quot;&gt;IAM 사용자 전용 로그인 URL&lt;/b&gt;로 접속하여 Admin-Dev로 로그인합니다.&lt;/li&gt;
&lt;li&gt;앞으로는 이 계정으로만 서버를 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;Root 계정 관리:&lt;/b&gt; Root 계정은 결제 수단 변경 등 아주 중요한 작업 외에는 절대 로그인하지 마세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kT7Ve/dJMcac3zRBO/N0XMHQLcDGQik36CZJJTr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kT7Ve/dJMcac3zRBO/N0XMHQLcDGQik36CZJJTr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kT7Ve/dJMcac3zRBO/N0XMHQLcDGQik36CZJJTr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkT7Ve%2FdJMcac3zRBO%2FN0XMHQLcDGQik36CZJJTr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1184&quot; height=&quot;205&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 MFA가 인증됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 로그인할때마다 휴대폰에 6자리 입력하면서 로그인하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;EC2 인스턴스 생성 (골격 만들기)&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;AWS 콘솔 접속&lt;/b&gt; &amp;gt; EC2 서비스 선택 &amp;gt; 인스턴스 시작 클릭&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;이름 및 태그:&lt;/b&gt; auto-game-server (관리하기 편한 이름)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;OS(AMI):&lt;/b&gt; Ubuntu Server 24.04 LTS (64비트) 선택 (가장 안정적임)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,3,0&quot;&gt;인스턴스 유형:&lt;/b&gt; t2.micro (프리티어 확인)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,4,0&quot;&gt;키 페어:&lt;/b&gt; 새 키 페어 생성 &amp;gt; RSA 방식, .pem 형식으로 다운로드 (보관 필수!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddPF3V/dJMcafeZOqx/6vmZXCZkzrM5hNtQqbZbn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddPF3V/dJMcafeZOqx/6vmZXCZkzrM5hNtQqbZbn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddPF3V/dJMcafeZOqx/6vmZXCZkzrM5hNtQqbZbn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddPF3V%2FdJMcafeZOqx%2F6vmZXCZkzrM5hNtQqbZbn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;987&quot; height=&quot;745&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;스토리지(EBS) 30GB 증설 (근육 붙이기)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;설정 위치:&lt;/b&gt; 인스턴스 생성 화면 하단의 스토리지 구성&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;변경 내용:&lt;/b&gt; 기본값 8 GiB를 **30 GiB**로 수정합니다.&lt;/li&gt;
&lt;li data-path-to-node=&quot;7,1,1&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,1,0&quot;&gt;주의:&lt;/b&gt; 30GB를 초과하면 비용이 발생하므로 정확히 30으로 맞추는 것이 포인트입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 네트워크 및 보안 그룹 (방어선 구축)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;9&quot;&gt;인스턴스 생성 시 네트워크 설정에서 보안 그룹 생성을 선택하고 아래 규칙을 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-path-to-node=&quot;10&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;유형&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;포트 범위&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;소스 유형&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,1,0,0&quot;&gt;&lt;b data-path-to-node=&quot;10,1,0,0&quot; data-index-in-node=&quot;0&quot;&gt;SSH&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,1,1,0&quot;&gt;22&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,1,2,0&quot;&gt;내 IP&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,1,3,0&quot;&gt;내 PC에서만 터미널 접속 허용 (보안)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,2,0,0&quot;&gt;&lt;b data-path-to-node=&quot;10,2,0,0&quot; data-index-in-node=&quot;0&quot;&gt;사용자 지정 TCP&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,2,1,0&quot;&gt;3000&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,2,2,0&quot;&gt;위치 무관(0.0.0.0/0)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,2,3,0&quot;&gt;NestJS 서버 포트 (유니티 클라이언트용)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,3,0,0&quot;&gt;&lt;b data-path-to-node=&quot;10,3,0,0&quot; data-index-in-node=&quot;0&quot;&gt;사용자 지정 TCP&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,3,1,0&quot;&gt;6379&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,3,2,0&quot;&gt;사용자 지정&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;10,3,3,0&quot;&gt;(선택) 외부에서 Redis 접속이 필요할 때만 개방&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bky8BE/dJMcahw5GsX/wd7Dj7vNjHBB4lNkOW6GlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bky8BE/dJMcahw5GsX/wd7Dj7vNjHBB4lNkOW6GlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bky8BE/dJMcahw5GsX/wd7Dj7vNjHBB4lNkOW6GlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbky8BE%2FdJMcahw5GsX%2Fwd7Dj7vNjHBB4lNkOW6GlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1008&quot; height=&quot;746&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;746&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;style7&quot; /&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;탄력적 IP(Elastic IP) 할당 및 연결 (필수)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;EC2는 기본적으로 '동적 IP'를 사용합니다. 즉, 서버를 재부팅하거나 인스턴스를 중지 후 다시 시작하면 &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;5&quot;&gt;IP 주소가 바뀝니다.&lt;/b&gt; 유니티 클라이언트에 서버 주소를 고정해두려면 '고정 IP'가 반드시 필요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;메뉴 이동:&lt;/b&gt; EC2 대시보드 왼쪽 사이드바 -&amp;gt; 네트워크 및 보안 -&amp;gt; 탄력적 IP 클릭.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;IP 할당:&lt;/b&gt; 오른쪽 상단 탄력적 IP 주소 할당 클릭 -&amp;gt; 할당 버튼 클릭.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;인스턴스 연결:&lt;/b&gt; 생성된 IP 주소를 체크 -&amp;gt; 작업 메뉴 -&amp;gt; 탄력적 IP 주소 연결 선택.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,1,0,0&quot;&gt;인스턴스:&lt;/b&gt; 내가 만든 auto-game-server 선택.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,1,1,0&quot;&gt;프라이빗 IP 주소:&lt;/b&gt; 해당 인스턴스의 내부 IP 선택.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,3,0&quot;&gt;확인:&lt;/b&gt; 이제 서버를 껐다 켜도 이 IP 주소는 절대 바뀌지 않습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;  인스턴스 생성 시 '보안 그룹 생성' 상세 설정 (Step-by-Step)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스 시작(Launch Instance) 화면 중간의 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;5&quot;&gt;[네트워크 설정]&lt;/b&gt; 부분에서 아래 순서대로 세팅하세요.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;1. 보안 그룹 생성 선택&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;보안 그룹 생성&lt;/b&gt; 항목을 체크합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;보안 그룹 이름:&lt;/b&gt; auto-game-sg (나중에 찾기 쉽게 이름을 꼭 지어주세요)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;설명:&lt;/b&gt; Game server security group for NestJS and Redis&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;2. 인바운드 보안 그룹 규칙 구성 (여기가 핵심!)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;기본으로 하나가 떠 있을 텐데, 오른쪽의 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;9&quot;&gt;[보안 그룹 규칙 추가]&lt;/b&gt; 버튼을 눌러 총 3개를 만듭니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;Rule 1: SSH (내 접속용)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;유형:&lt;/b&gt; SSH&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;프로토콜/포트:&lt;/b&gt; TCP / 22&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;소스 유형:&lt;/b&gt; &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;11,2,0&quot;&gt;내 IP&lt;/b&gt; (가장 중요!)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,3,0&quot;&gt;설명:&lt;/b&gt; Admin SSH Access&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;Rule 2: NestJS (게임 통신용)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;유형:&lt;/b&gt; 사용자 지정 TCP&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;프로토콜/포트:&lt;/b&gt; TCP / 3000&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;소스 유형:&lt;/b&gt; &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;13,2,0&quot;&gt;위치 무관(Anywhere)&lt;/b&gt; (0.0.0.0/0)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,3,0&quot;&gt;설명:&lt;/b&gt; Unity Client Communication&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;Rule 3: Redis (내부 DB용)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;유형:&lt;/b&gt; 사용자 지정 TCP&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;프로토콜/포트:&lt;/b&gt; TCP / 6379&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,2,0&quot;&gt;소스 유형:&lt;/b&gt; &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;15,2,0&quot;&gt;사용자 지정&lt;/b&gt; * &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;15,2,0&quot;&gt;소스:&lt;/b&gt; 칸을 클릭하면 나오는 리스트에서 **현재 생성 중인 보안 그룹 이름(또는 ID)**을 선택합니다. 만약 아직 안 뜨면 일단 내 IP로 해두고 생성 후에 수정하면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,3,0&quot;&gt;설명:&lt;/b&gt; Internal Redis Access&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;16&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;  왜 '그냥' 만들 때 이 설정을 해야 하나요? (납득 포인트)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;자동 생성의 함정:&lt;/b&gt; 아무 설정 없이 SSH를 허용하면 소스가 0.0.0.0/0(모두)으로 잡힙니다. 이러면 전 세계 해커들이 내 서버 대문에 매일 노크를 하게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;한 번에 끝내기:&lt;/b&gt; 생성 단계에서 **내 IP**와 **3000번 포트**만 정확히 잡아줘도 보안의 80%는 성공입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1497&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkrzCV/dJMcacCx7HY/NTI7nZUoz5PxlnZjUUKzzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkrzCV/dJMcacCx7HY/NTI7nZUoz5PxlnZjUUKzzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkrzCV/dJMcacCx7HY/NTI7nZUoz5PxlnZjUUKzzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkrzCV%2FdJMcacCx7HY%2FNTI7nZUoz5PxlnZjUUKzzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1497&quot; height=&quot;814&quot; data-origin-width=&quot;1497&quot; data-origin-height=&quot;814&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;style7&quot; /&gt;
&lt;h3 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;서버 원격 접속 및 초기 환경 구축 (Access &amp;amp; Runtime)&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;1. 내 PC에서 서버 접속하기 (SSH)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;다운로드받은 .pem 키 파일이 있는 폴더에서 터미널(CMD 또는 PowerShell)을 열고 아래 명령어를 입력합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjO-K74rr-TAxUAAAAAHQAAAAAQoQY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;터미널 명령어&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 1. (Windows 전용) 키 파일 권한 제한 (앞서 설명한 icacls 명령어)
icacls &quot;your-key.pem&quot; /inheritance:r
icacls &quot;your-key.pem&quot; /grant:r &quot;%username%:R&quot;

# 2. SSH 접속 실행
# ubuntu는 AWS 우분투 이미지의 기본 사용자 이름입니다.
ssh -i &quot;your-key.pem&quot; ubuntu@[탄력적 IP 주소]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;7&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;7,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0&quot;&gt;성공 시:&lt;/b&gt; Welcome to Ubuntu... 라는 문구와 함께 커서가 ubuntu@ip-xxx-xx-xx-xx:~$ 로 바뀝니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9&quot;&gt;2. 필수 런타임 설치 (Node.js &amp;amp; npm)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;NestJS 서버는 Node.js 위에서 돌아갑니다. 최신 LTS 버전을 설치해 줍니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjO-K74rr-TAxUAAAAAHQAAAAAQogY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 패키지 매니저 업데이트
sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y

# Node.js 20.x (LTS) 설치 스크립트 실행
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

# 설치 확인
node -v
npm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13&quot;&gt;3. Redis 인메모리 데이터베이스 구축&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;게임의 랭킹이나 실시간 세션 데이터를 빠르게 처리하기 위해 Redis를 설치합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjO-K74rr-TAxUAAAAAHQAAAAAQowY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# Redis 설치
sudo apt install redis-server -y

# Redis 실행 및 자동 실행 설정
sudo systemctl start redis-server
sudo systemctl enable redis-server

# 작동 테스트 (PONG이 나오면 성공)
redis-cli ping
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;16&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;4. PM2 (Process Manager) 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;우리가 터미널 창을 닫아도 서버가 죽지 않고 24시간 돌아가게 해주는 필수 도구입니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjO-K74rr-TAxUAAAAAHQAAAAAQpAY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;# 전역(global)으로 PM2 설치
sudo npm install -g pm2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2&quot;&gt;PM2 프로세스 관리 및 모니터링 마스터하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;PM2는 서버가 예상치 못한 에러로 종료되어도 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;3&quot;&gt;자동으로 재시작&lt;/b&gt;해주며, 터미널을 닫아도 백그라운드에서 서비스를 유지해주는 든든한 관리자입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;1. 서버 실행 및 등록&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;처음 프로젝트를 빌드한 후 서버를 올릴 때 사용합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjO-K74rr-TAxUAAAAAHQAAAAAQugY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;# dist/main.js 파일을 &quot;auto-game-server&quot;라는 이름으로 실행
pm2 start dist/main.js --name &quot;auto-game-server&quot;

# 서버 재부팅 시에도 PM2가 이 프로세스를 기억하고 자동 실행하게 설정
pm2 save
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;7&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;2. 실시간 상태 모니터링 (가장 많이 사용)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;서버가 잘 돌아가고 있는지, 에러는 없는지 확인할 때 필수입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;상태 요약:&lt;/b&gt; pm2 status
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;online 상태인지, 메모리와 CPU 점유율은 어떤지 한눈에 보여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;실시간 로그 감시:&lt;/b&gt; pm2 logs auto-game-server
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,1,0,0&quot;&gt;가장 중요합니다.&lt;/b&gt; 유니티 클라이언트에서 접속 에러가 나거나 서버 내부 로직 오류(500 Error 등)가 발생하면 여기서 실시간으로 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;대시보드 모드:&lt;/b&gt; pm2 monit
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널 화면을 화려한 대시보드로 바꿔줍니다. 리소스 사용량을 시각적으로 모니터링할 때 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;11&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;3. 프로세스 제어 (업데이트 및 수정 시)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;코드를 수정하거나 서버 설정을 바꿨을 때 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;재시작:&lt;/b&gt; pm2 restart auto-game-server
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;git pull로 코드를 업데이트하거나 .env 파일을 수정한 후 즉시 반영할 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;중지 및 삭제:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pm2 stop auto-game-server: 잠시 서버를 끕니다.&lt;/li&gt;
&lt;li&gt;pm2 delete auto-game-server: 관리 목록에서 완전히 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;15&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16&quot;&gt;4. PM2 관리 명령어 요약표&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;17&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;실행 명령어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;비고&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0,0&quot;&gt;최초 실행&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,1,1,0&quot;&gt;pm2 start [경로] --name [이름]&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,1,2,0&quot;&gt;프로세스 이름 지정 권장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0,0&quot;&gt;에러 추적&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,2,1,0&quot;&gt;pm2 logs [이름]&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,2,2,0&quot;&gt;트러블슈팅의 1순위&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,3,0,0&quot;&gt;상태 확인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,3,1,0&quot;&gt;pm2 status&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,3,2,0&quot;&gt;서비스 생존 여부 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,4,0,0&quot;&gt;코드 반영&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,4,1,0&quot;&gt;pm2 restart [이름]&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,4,2,0&quot;&gt;수정사항 즉시 적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,5,0,0&quot;&gt;부팅 자동화&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,5,1,0&quot;&gt;pm2 save&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;17,5,2,0&quot;&gt;AWS 인스턴스 재부팅 대비&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>AWS</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/122</guid>
      <comments>https://codingisland.tistory.com/122#entry122comment</comments>
      <pubDate>Fri, 27 Mar 2026 17:21:43 +0900</pubDate>
    </item>
    <item>
      <title>[웹] AI를 활용한 웹 사이트 제작 및 자동배포 CI/CD</title>
      <link>https://codingisland.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;최근 AI를 활용한 코딩이 부쩍 늘었기에 이 기술을 활용해 나만의 사이트를 제작해보고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용한 기술은&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;웹 사이트 언어: TypeScript, React, Next.js&lt;br /&gt;AI Assist : Gemini Code Assist&lt;br /&gt;CI/CD: Git, Vercel&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;언어는&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;1&quot;&gt;TypeScript&lt;/b&gt;와 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;1&quot;&gt;JSX&lt;/b&gt;가 결합된 &lt;b&gt;React(Next.js)&lt;/b&gt;로 진행했다.&lt;span style=&quot;color: #666666; font-size: 1em; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771661998014&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 명령어는 개발자 모드로 실행하는 명령어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만드는 웹이 어떤 모습을 해 나가고 있는지 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드 화면&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjzvq2/dJMcaaRRG3c/uZAYCpVjHfJFpOD7T4kHs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjzvq2/dJMcaaRRG3c/uZAYCpVjHfJFpOD7T4kHs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjzvq2/dJMcaaRRG3c/uZAYCpVjHfJFpOD7T4kHs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjzvq2%2FdJMcaaRRG3c%2FuZAYCpVjHfJFpOD7T4kHs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1261&quot; height=&quot;549&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;549&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;size18&quot;&gt;난 여태 게임개발을 위주로 해왔기에 바로 실전으로 들어가지 못한다. 하지만 AI가 있기에 가능하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Gemini Code Assistant&amp;nbsp;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1809&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMZ2cZ/dJMcahKdotr/HceNAD6DYMN1SAkSSgG121/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMZ2cZ/dJMcahKdotr/HceNAD6DYMN1SAkSSgG121/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMZ2cZ/dJMcahKdotr/HceNAD6DYMN1SAkSSgG121/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMZ2cZ%2FdJMcahKdotr%2FHceNAD6DYMN1SAkSSgG121%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1809&quot; height=&quot;811&quot; data-origin-width=&quot;1809&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AI에게 자연어로 &quot;기능이나 페이지를 구현해줘&quot; 라고하면 대강 틀을 잡아주며 프로젝트 전체를 훑어서 다른 파일에 호환도 유지시키며 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쓰면서 느낀거지만 프로그래밍 &quot;언어&quot;라고 느끼는게 우리가 쓰는 언어와 가장많이 가까워진 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리가 애니를 많이 봐와서 일본어 청해가 되지만 직접 말하려고 하면 듣던 것 처럼 나오지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AI가 짜준 코드를 보면 이게 어떤 기능을 하는지 이해할 순 있지만 막상 똑같이 코딩 하려하면 하지 못하는 것처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일반 언어를 공부하는 것 처럼 너무 흡사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;난 AI가 짜준 코드를 보고 이런 기능을 하겠구나~ 하며 쓱 훑어본 뒤 수정할 것만 추가로 프롬프트로 요청하며 웹을 만들어나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;홈페이지 화면&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&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;1292&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DXSmr/dJMcag5Boi7/kIr87OqrloJ2fEYAScoRc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DXSmr/dJMcag5Boi7/kIr87OqrloJ2fEYAScoRc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DXSmr/dJMcag5Boi7/kIr87OqrloJ2fEYAScoRc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDXSmr%2FdJMcag5Boi7%2FkIr87OqrloJ2fEYAScoRc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1292&quot; height=&quot;789&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;789&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;size18&quot;&gt;이렇게 웹이 거의다 완성이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 &lt;b&gt;Git&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;761&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RqK7x/dJMcaibhFFd/GWTjtXmWkqkD3j2GTzPdX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RqK7x/dJMcaibhFFd/GWTjtXmWkqkD3j2GTzPdX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RqK7x/dJMcaibhFFd/GWTjtXmWkqkD3j2GTzPdX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRqK7x%2FdJMcaibhFFd%2FGWTjtXmWkqkD3j2GTzPdX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;504&quot; data-origin-width=&quot;761&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;size18&quot;&gt;프로젝트 파일 자체를 Root에 모두 배치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 Vercel로 배포를 시작하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;vercel 로그인 페이지&lt;/blockquote&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;819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crCN7c/dJMcacIUiQt/kZMKqwvbqLLpcA18I6obTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crCN7c/dJMcacIUiQt/kZMKqwvbqLLpcA18I6obTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crCN7c/dJMcacIUiQt/kZMKqwvbqLLpcA18I6obTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrCN7c%2FdJMcacIUiQt%2FkZMKqwvbqLLpcA18I6obTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;819&quot; data-origin-width=&quot;1473&quot; data-origin-height=&quot;819&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;원하는 방법으로 로그인을 해주면 된다. 웬만하면 구글이나 깃허브 로그인을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;깃 리포지토리 연동&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;807&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brlsjw/dJMcai3lCOI/syhJ4pflmlxmnOnvObqgWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brlsjw/dJMcai3lCOI/syhJ4pflmlxmnOnvObqgWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brlsjw/dJMcai3lCOI/syhJ4pflmlxmnOnvObqgWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrlsjw%2FdJMcai3lCOI%2FsyhJ4pflmlxmnOnvObqgWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;807&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;807&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;size18&quot;&gt;처음 저 빨간 테두리에 깃을 연결하라고 뜰것이다. 거기서 &lt;u&gt;리포지토리를 하나만 가져오게&lt;/u&gt; 토글설정을 바꿔주어 아까 올린 깃의 레포지토리를 선택한 뒤 저 &lt;b&gt;Import&lt;/b&gt;를 클릭해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로젝트 생성 마무리&lt;/blockquote&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;685&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z3uk3/dJMcaducCI7/o1ehUDnWjVkSgxttseV4e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z3uk3/dJMcaducCI7/o1ehUDnWjVkSgxttseV4e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z3uk3/dJMcaducCI7/o1ehUDnWjVkSgxttseV4e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ3uk3%2FdJMcaducCI7%2Fo1ehUDnWjVkSgxttseV4e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;685&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;685&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;저 가려진것들은 신경쓸 필요 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Root Directory를 우리가 Repository의 Root에 넣었으니 따로 설정해줄 필요 없다. 밑에 &lt;b&gt;Deploy&lt;/b&gt;를 바로 눌러주고 1~2분 기다리면 바로 배포된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 끝이다. &lt;u&gt;수정 할게 있으면 깃에 push만 시켜줘도 Vercel이 이를 감지해서 자동으로 변경사항을 적용&lt;/u&gt;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;깃을 통해 버전관리&lt;/b&gt;도 하고 &lt;b&gt;Vercel로 자동배포&lt;/b&gt;까지 모두 한 셈이다. CI/CD를 구축했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;로그 확인&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UPF3t/dJMcajgTkpp/r655EDQux8j9nfjUsRWllk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UPF3t/dJMcajgTkpp/r655EDQux8j9nfjUsRWllk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UPF3t/dJMcajgTkpp/r655EDQux8j9nfjUsRWllk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUPF3t%2FdJMcajgTkpp%2Fr655EDQux8j9nfjUsRWllk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;705&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그저 &quot;딸깍&quot; 몇번으로 홈페이지도 만들고 CI/CD도 모두 구축했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://rudisland-portfolio.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rudisland-portfolio.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771662878964&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;김경훈 포트폴리오 rud_island-Portfolio&quot; data-og-description=&quot;About Me 위치 제주, 대한민국 이메일 rudgns4541@gmail.com 학력 공주대학교 컴퓨터공학전공 전화번호 010-9345-7509 게임 개발로 시작해서 앱, 웹 개발까지 다양한 작품을 만들어온 개발자입니다. 다양한 &quot; data-og-host=&quot;rudisland-portfolio.vercel.app&quot; data-og-source-url=&quot;https://rudisland-portfolio.vercel.app/&quot; data-og-url=&quot;https://rudisland-portfolio.vercel.app/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TuO9x/dJMb81GTUKP/q9xcjKXg3qiH71k7LjwP00/img.jpg?width=1000&amp;amp;height=1275&amp;amp;face=0_0_1000_1275,https://scrap.kakaocdn.net/dn/odatm/dJMb88F1Bms/eW9449hGzIMSyHpTA8pw11/img.jpg?width=1130&amp;amp;height=942&amp;amp;face=0_0_1130_942,https://scrap.kakaocdn.net/dn/pgIRR/dJMb85vLBZB/mmP9yQNQtOfKwI7LyhEyt1/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024&quot;&gt;&lt;a href=&quot;https://rudisland-portfolio.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rudisland-portfolio.vercel.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TuO9x/dJMb81GTUKP/q9xcjKXg3qiH71k7LjwP00/img.jpg?width=1000&amp;amp;height=1275&amp;amp;face=0_0_1000_1275,https://scrap.kakaocdn.net/dn/odatm/dJMb88F1Bms/eW9449hGzIMSyHpTA8pw11/img.jpg?width=1130&amp;amp;height=942&amp;amp;face=0_0_1130_942,https://scrap.kakaocdn.net/dn/pgIRR/dJMb85vLBZB/mmP9yQNQtOfKwI7LyhEyt1/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024');&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;김경훈 포트폴리오 rud_island-Portfolio&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;About Me 위치 제주, 대한민국 이메일 rudgns4541@gmail.com 학력 공주대학교 컴퓨터공학전공 전화번호 010-9345-7509 게임 개발로 시작해서 앱, 웹 개발까지 다양한 작품을 만들어온 개발자입니다. 다양한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rudisland-portfolio.vercel.app&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;size18&quot;&gt;도메인도 매년 일정 금액을 지불하면 구매할 수 있다. 1~10만원대 하니까 참고!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;.....&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;진짜 개발자는 끝장났나보다.&lt;/p&gt;</description>
      <category>프로젝트/웹프로그래밍</category>
      <category>ci/cd</category>
      <category>Next.js</category>
      <category>react</category>
      <category>TypeScript</category>
      <category>vercel</category>
      <category>웹</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/121</guid>
      <comments>https://codingisland.tistory.com/121#entry121comment</comments>
      <pubDate>Sat, 21 Feb 2026 17:35:58 +0900</pubDate>
    </item>
    <item>
      <title>[앱 배포] Work_Island 근무관리 앱 출시</title>
      <link>https://codingisland.tistory.com/120</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 앱 출시&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;처음으로 앱을 출시하게 되었습니다. 뿌뿌~~&amp;nbsp;        &lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;text-align: center; caret-color: transparent; font-size: 16px; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dna/pceMu/dJMcagj6CfM/AAAAAAAAAAAAAAAAAAAAAGagHTY61YZ7QuJBLNriF68OdyP2ibtDiG4G2GmBqtIj/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1772290799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=tCcetWlnUQIjiH0CqyDXyIPm3dc%3D&quot; width=&quot;647&quot; height=&quot;711&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;868&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Kasi-Koi Jpop 노래 번역 앱을 만들고나서 Flutter의 가능성을 느꼈고, 안해본 것들을 해보고자 시작했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Kasi-Koi : &lt;a title=&quot;링크&quot; href=&quot;https://codingisland.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codingisland.tistory.com/118&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770692758390&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] AI로 노래가사 해석하는 앱개발 - 3 (마무리)&quot; data-og-description=&quot;지난글 링크https://codingisland.tistory.com/116 평소 Jpop 많이 들음.영감: 유튜브 쇼츠 카지노 차무식이 팝송 500곡 외웠다길래 &amp;quot; data-og-host=&amp;quot;codingisland.tistory.com&amp;quot; data-og-source-url=&amp;quot;https://codingisland.tistory.com/116&amp;quot;&quot; data-og-host=&quot;codingisland.tistory.com&quot; data-og-source-url=&quot;https://codingisland.tistory.com/118&quot; data-og-url=&quot;https://codingisland.tistory.com/118&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hPNhj/dJMb9cBDWK5/6SIgMXei9IsEPgIO9ws3iK/img.jpg?width=360&amp;amp;height=741&amp;amp;face=0_0_360_741,https://scrap.kakaocdn.net/dn/kGznB/dJMb8Qeh1KH/2gLSQNVWPNe7wlFyQ5zbh1/img.jpg?width=360&amp;amp;height=741&amp;amp;face=0_0_360_741,https://scrap.kakaocdn.net/dn/1FnZ5/dJMb88eWsIa/4QF43OMGHxZG3wf4MOcOg1/img.png?width=912&amp;amp;height=428&amp;amp;face=0_0_912_428&quot;&gt;&lt;a href=&quot;https://codingisland.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingisland.tistory.com/118&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hPNhj/dJMb9cBDWK5/6SIgMXei9IsEPgIO9ws3iK/img.jpg?width=360&amp;amp;height=741&amp;amp;face=0_0_360_741,https://scrap.kakaocdn.net/dn/kGznB/dJMb8Qeh1KH/2gLSQNVWPNe7wlFyQ5zbh1/img.jpg?width=360&amp;amp;height=741&amp;amp;face=0_0_360_741,https://scrap.kakaocdn.net/dn/1FnZ5/dJMb88eWsIa/4QF43OMGHxZG3wf4MOcOg1/img.png?width=912&amp;amp;height=428&amp;amp;face=0_0_912_428');&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] AI로 노래가사 해석하는 앱개발 - 3 (마무리)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지난글 링크https://codingisland.tistory.com/116 평소 Jpop 많이 들음.영감: 유튜브 쇼츠 카지노 차무식이 팝송 500곡 외웠다길래 &quot; data-og-host=&quot;codingisland.tistory.com&quot; data-og-source-url=&quot;https://codingisland.tistory.com/116&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingisland.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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 기획단계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Kasi-koi를 개발할때 기획서가 없으니 개발과정에서 자꾸 기능이 추가되고 이거였나? 싶고 하다보니 개발시간이 늘어났던 경험때문에 반드시 &lt;b&gt;기획부터 제대로 잡고가자는&lt;/b&gt; 생각으로 Word 부터 켰습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XTnox/dJMcaiCcYEB/cez2b4CtqvRJKuJKz35lw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XTnox/dJMcaiCcYEB/cez2b4CtqvRJKuJKz35lw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XTnox/dJMcaiCcYEB/cez2b4CtqvRJKuJKz35lw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXTnox%2FdJMcaiCcYEB%2Fcez2b4CtqvRJKuJKz35lw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;771&quot; height=&quot;538&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;851&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;기능 명세서&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-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 개발단계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;저는 Visual Studio Code(VSCode)로 AI를 적극 활용해 개발했습니다. 바이브 코딩 열심히 한거죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;지난 Kasi-koi에서 느꼈던 소스코드들의 구조문제를 이번 프로젝트에선 제대로 하고자&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;직관적으로 찾을 수 있게 각 기능의 화면을 기준으로 구조를 잡았습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;첫화면 - Intro/위젯, 화면, DAO&lt;/li&gt;
&lt;li&gt;근무탭 - Work/위젯,화면, DAO&lt;/li&gt;
&lt;li&gt;스케줄탭- Schedule/위젯, 화면, DAO&lt;/li&gt;
&lt;li&gt;기록탭 - WorkLog/위젯,화면, DAO&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각 화면마다 나누어 그 화면에 필요한 소스코드들로만 배치하여 협업했을때 &lt;b&gt;각 화면을 담당하게 되었을때의 구조&lt;/b&gt;라는 점에 신경썼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여태 AI를 인터넷창 켜서 소스코드와 의도를 전달하며 하나씩 만들어나갔는데 VSCode로 Gemini Code Assist를 연결하니 제 프로젝트 파일을 모두 보며 제 의도를 제대로 잡더라고요 코드들도 알아서 고쳐주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;진짜 블리츠크랭크의 대사가 조만간인듯합니다. &quot;인간 시대의 끝이 도래했다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 테스트 단계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;테스트 해볼게 너~무 많아서 혼자 해보기 너무 싫어서 &lt;b&gt;자동화 QA&lt;/b&gt; 시키는 방법이 없나 찾아봤는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Firebase Test Lab&lt;/b&gt;이란게 있더라고요 그래서 바로 넣었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용방법은 아래 링크&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://codingisland.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codingisland.tistory.com/119&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770693839199&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;[AI] 앱 테스트 자동화 Firebase Test Lab 사용해보기&quot; data-og-description=&quot;Firebase Test Lab 이란앱을 등록하면 구글의 AI가 알아서 버튼들을 눌러보며 크래시(앱꺼짐)이 발생하는지 테스트해주는 로봇(?)설정하는 방법1. 구글 콘솔에 프로젝트 생성 프로젝트 이름은 직관적&quot; data-og-host=&quot;codingisland.tistory.com&quot; data-og-source-url=&quot;https://codingisland.tistory.com/119&quot; data-og-url=&quot;https://codingisland.tistory.com/119&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yy29p/dJMb8WevqGD/Z0uJ8dAe5rzzBAYf4w42q1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cypyJT/dJMb8RRNKoG/HyKuvJtypj0DU5D1C61EmK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JDLVq/dJMb8YXHiIP/3EKHqzSO7Tenid6lUn5KzK/img.png?width=854&amp;amp;height=686&amp;amp;face=0_0_854_686&quot;&gt;&lt;a href=&quot;https://codingisland.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingisland.tistory.com/119&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yy29p/dJMb8WevqGD/Z0uJ8dAe5rzzBAYf4w42q1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cypyJT/dJMb8RRNKoG/HyKuvJtypj0DU5D1C61EmK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JDLVq/dJMb8YXHiIP/3EKHqzSO7Tenid6lUn5KzK/img.png?width=854&amp;amp;height=686&amp;amp;face=0_0_854_686');&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;[AI] 앱 테스트 자동화 Firebase Test Lab 사용해보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Firebase Test Lab 이란앱을 등록하면 구글의 AI가 알아서 버튼들을 눌러보며 크래시(앱꺼짐)이 발생하는지 테스트해주는 로봇(?)설정하는 방법1. 구글 콘솔에 프로젝트 생성 프로젝트 이름은 직관적&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingisland.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;size18&quot;&gt;프로젝트를 깃에 push하면 Action에서 이를 감지해서 Firebase 콘솔에 Test Lab에 가면 어떤 테스트를 했는지 스크린샷과 동영상 등 기록이 남아 보입니다. 그걸로 체크할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 배포단계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;플레이 스토어에 올리려 했으나... 비공개 테스트로 12명?을 데려와서 2주동안 테스트를 진행해야 겨우 공개 배포로 바꿀 수 있다기에... 친구도 없고 시간도 촉박해서 다음에..하기로... 12명은 크몽에서도 돈 내고 해주는게 있긴하던데 당장 돈도 없어서 전부 무료인 것만 썼다...예요...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biOhMq/dJMcagErnmF/W0iUK0lhKBde75sCNFdtxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biOhMq/dJMcagErnmF/W0iUK0lhKBde75sCNFdtxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biOhMq/dJMcagErnmF/W0iUK0lhKBde75sCNFdtxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiOhMq%2FdJMcagErnmF%2FW0iUK0lhKBde75sCNFdtxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;921&quot; height=&quot;333&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마지막 원스토어 링크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://m.onestore.co.kr/v2/ko-kr/app/0001004309&quot;&gt;https://m.onestore.co.kr/v2/ko-kr/app/0001004309&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770694155076&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;Work Island - 사장님과 알바생을 위한 똑똑한 급여정산 - 원스토어&quot; data-og-description=&quot;복잡한 주휴수당, 야간수당, 세금까지 한 번에! 시급&amp;middot;일급&amp;middot;월급 계산과 근무 일정 관리를 워크아일랜드에서 시작하세요.&quot; data-og-host=&quot;m.onestore.co.kr&quot; data-og-source-url=&quot;https://m.onestore.co.kr/v2/ko-kr/app/0001004309&quot; data-og-url=&quot;https://m.onestore.co.kr/v2/ko-kr/app/0001004309&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TBHH6/dJMb8XR1pxo/ZrTKYc36lR5jiCy3psTokK/img.jpg?width=1024&amp;amp;height=578&amp;amp;face=0_0_1024_578,https://scrap.kakaocdn.net/dn/VgSsw/dJMb87NSbMe/uV6902dQp0X7JXLJKjRA11/img.jpg?width=1024&amp;amp;height=578&amp;amp;face=0_0_1024_578,https://scrap.kakaocdn.net/dn/Aomrx/dJMb87NSbMd/Ye0w4mFwbTTDK2BQaFAHc0/img.jpg?width=324&amp;amp;height=576&amp;amp;face=0_0_324_576&quot;&gt;&lt;a href=&quot;https://m.onestore.co.kr/v2/ko-kr/app/0001004309&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.onestore.co.kr/v2/ko-kr/app/0001004309&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TBHH6/dJMb8XR1pxo/ZrTKYc36lR5jiCy3psTokK/img.jpg?width=1024&amp;amp;height=578&amp;amp;face=0_0_1024_578,https://scrap.kakaocdn.net/dn/VgSsw/dJMb87NSbMe/uV6902dQp0X7JXLJKjRA11/img.jpg?width=1024&amp;amp;height=578&amp;amp;face=0_0_1024_578,https://scrap.kakaocdn.net/dn/Aomrx/dJMb87NSbMd/Ye0w4mFwbTTDK2BQaFAHc0/img.jpg?width=324&amp;amp;height=576&amp;amp;face=0_0_324_576');&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;Work Island - 사장님과 알바생을 위한 똑똑한 급여정산 - 원스토어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 주휴수당, 야간수당, 세금까지 한 번에! 시급&amp;middot;일급&amp;middot;월급 계산과 근무 일정 관리를 워크아일랜드에서 시작하세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;m.onestore.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 data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;만괂부&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;갤럭시 스토어도 가능하면 출시 예정.&lt;/p&gt;</description>
      <category>프로젝트/출시물</category>
      <category>Flutter</category>
      <category>배포</category>
      <category>앱출시</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/120</guid>
      <comments>https://codingisland.tistory.com/120#entry120comment</comments>
      <pubDate>Tue, 10 Feb 2026 12:31:34 +0900</pubDate>
    </item>
    <item>
      <title>[AI] 앱 테스트 자동화 Firebase Test Lab 사용해보기</title>
      <link>https://codingisland.tistory.com/119</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Firebase Test Lab 이란&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;앱을 등록하면 구글의 AI가 알아서 버튼들을 눌러보며 크래시(앱꺼짐)이 발생하는지 테스트해주는 로봇(?)&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정하는 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 구글 콘솔에 프로젝트 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjc6nh/dJMb996lypA/04zqGmXjYxvg8y1vwfiVyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjc6nh/dJMb996lypA/04zqGmXjYxvg8y1vwfiVyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjc6nh/dJMb996lypA/04zqGmXjYxvg8y1vwfiVyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjc6nh%2FdJMb996lypA%2F04zqGmXjYxvg8y1vwfiVyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1083&quot; height=&quot;456&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;456&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;size18&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;h3 data-ke-size=&quot;size23&quot;&gt;2. Firebase 콘솔에서 프로젝트 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VuqQG/dJMcaiIWoSu/Ot88WMr9IpCvUOz7C8KSEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VuqQG/dJMcaiIWoSu/Ot88WMr9IpCvUOz7C8KSEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VuqQG/dJMcaiIWoSu/Ot88WMr9IpCvUOz7C8KSEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVuqQG%2FdJMcaiIWoSu%2FOt88WMr9IpCvUOz7C8KSEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;452&quot; height=&quot;354&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Firebase콘솔 사이트에 들어가서 [새 Firebase 프로젝트 만들기]를 클릭 후 밑에 Google console 프로젝트에서 가져오기로 위에서 생성한 프로젝트를 선택해 계속 생성을 진행합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 프로젝트에 Firebase 관련 연결&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIzoaG/dJMcafZLWqG/9yvKsUKcIN40gYYZw84Qkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIzoaG/dJMcafZLWqG/9yvKsUKcIN40gYYZw84Qkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIzoaG/dJMcafZLWqG/9yvKsUKcIN40gYYZw84Qkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIzoaG%2FdJMcafZLWqG%2F9yvKsUKcIN40gYYZw84Qkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;353&quot; height=&quot;132&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아마 저 만들기 들어가면 패키지명을 입력하라고 할텐데&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;안드로이드는 &lt;b&gt;프로젝트파일명/android/app/build.gradle.kts&lt;/b&gt; 에&lt;/p&gt;
&lt;pre id=&quot;code_1770367586868&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    namespace = &quot;com.xxxxxx.패키지명&quot;
    .....
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런식으로 이름이 있을겁니다 저 namespace 옆에 &quot;&quot;안에 있는 것을 입력합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 build.gradle.kts에 뭔가 추가하라고 2개 있을텐데&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;android/build.gradle.kts &lt;/b&gt;에는 아래 코드를 추가하고&lt;/p&gt;
&lt;pre id=&quot;code_1770368695486&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    // Google 서비스 플러그인 버전을 정의합니다.
    id(&quot;com.google.gms.google-services&quot;) version &quot;4.4.4&quot; apply false
}&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;size18&quot;&gt;&lt;b&gt;android/app/build.gradle.kts&lt;/b&gt; 에는&lt;/p&gt;
&lt;pre id=&quot;code_1770368741253&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id(&quot;com.android.application&quot;)
    id(&quot;kotlin-android&quot;)
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
    id(&quot;dev.flutter.flutter-gradle-plugin&quot;)

    id(&quot;com.google.gms.google-services&quot;) #추가해야될거
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1770368785480&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    // Firebase BoM (버전 관리자) 추가
    implementation(platform(&quot;com.google.firebase:firebase-bom:33.1.2&quot;))
    
    // 사용하고 싶은 서비스 추가 (예: 분석기)
    implementation(&quot;com.google.firebase:firebase-analytics&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 2개를 추가해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추가하게되면&lt;span style=&quot;background-color: #f3c000;&quot;&gt; google-service.json&lt;/span&gt; 을 설치받게 될텐데 이걸&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&quot;프로젝트명/android/app/google-serivce.json&quot; 으로 저장해둡니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;h3 data-ke-size=&quot;size23&quot;&gt;4. 구글 콘솔 서비스 계정 만들기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgQY0d/dJMcaaqEgIV/WSPb1NWyOHdHF9Re1AiAr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgQY0d/dJMcaaqEgIV/WSPb1NWyOHdHF9Re1AiAr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgQY0d/dJMcaaqEgIV/WSPb1NWyOHdHF9Re1AiAr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgQY0d%2FdJMcaaqEgIV%2FWSPb1NWyOHdHF9Re1AiAr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;854&quot; height=&quot;686&quot; data-origin-width=&quot;854&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;size18&quot;&gt;여기에 권한으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기본 &amp;gt; 편집자&lt;/span&gt; 가 있을겁니다 추가해주고 구성원은 패스하고 [완료]를 누릅니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;621&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhu9HQ/dJMcacPxE3z/2Q9UxecIML038ar2Sk6hJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhu9HQ/dJMcacPxE3z/2Q9UxecIML038ar2Sk6hJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhu9HQ/dJMcacPxE3z/2Q9UxecIML038ar2Sk6hJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdhu9HQ%2FdJMcacPxE3z%2F2Q9UxecIML038ar2Sk6hJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;467&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;543&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHaLLH/dJMcaioGRkx/91U77NIRvZOwyNfsFQqv50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHaLLH/dJMcaioGRkx/91U77NIRvZOwyNfsFQqv50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHaLLH/dJMcaioGRkx/91U77NIRvZOwyNfsFQqv50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHaLLH%2FdJMcaioGRkx%2F91U77NIRvZOwyNfsFQqv50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;273&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JSON으로 만들어줍니다. 이걸 깃허브 Secrets에 등록해야 합니다. 이때 같이 등록할 3가지가 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 깃허브 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 깃허브에 프로젝트를 올려야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Repository 생성 설명은 건너뛰고&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Repository폴더에 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;.github/workflows/robo_test.yml&lt;/span&gt; 을 생성해 줍니다. 그리고 아래 코드를 넣어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770369236767&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Automated Robo QA

on:
  workflow_dispatch: # 수동 실행 버튼 활성화 (무료 횟수 관리용) [cite: 2026-01-24]
  # push: 
  #   branches: [ main, develop ] # 자동 실행을 막기 위해 주석 처리함 [cite: 2026-01-22]

# [추가] 같은 테스트가 여러 번 눌려도 하나만 실행함 (자원 낭비 방지)
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  run-robo-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 1. Flutter 환경 설정
      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: 'stable'

    # [추가되는 부분] 금고에서 설정을 꺼내 파일을 만듭니다.
      - name: Create google-services.json
        run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' &amp;gt; android/app/google-services.json

      # 2. 앱 빌드 (테스트용 APK 생성)
      - name: Build Debug APK
        run: |
          flutter pub get
          flutter build apk --debug

      # 3. 구글 클라우드 인증 (금고에서 열쇠 꺼내기)
      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      # 4. Firebase Test Lab 로봇 출동
      - name: Run Robo Test
        run: |
          gcloud firebase test android run \
            --type robo \
            --app build/app/outputs/flutter-apk/app-debug.apk \
            --device model=oriole,version=33,locale=ko,orientation=portrait \
            --timeout 1m \
            --project ${{ secrets.이름맞추기 }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이걸 그대로 복붙해서 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 프로젝트들도 기존 프로젝트폴더에서 다 꺼내서 Repository 최상위 폴더로 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼 main이 이렇게 보일겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWObUc/dJMcachHpjD/QPD5bQS2vnH2TpDX2Dmun0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWObUc/dJMcachHpjD/QPD5bQS2vnH2TpDX2Dmun0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWObUc/dJMcachHpjD/QPD5bQS2vnH2TpDX2Dmun0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWObUc%2FdJMcachHpjD%2FQPD5bQS2vnH2TpDX2Dmun0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;223&quot; height=&quot;603&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러면 폴더 설정은 끝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 후 깃허브 홈페이지에 프로젝트&amp;gt;설정에 가면 이 페이지가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;왼쪽 탭에 Secrets and variables &amp;gt; Actions에서 New repository secret으로 3가지를 추가합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1. 구글 콘솔에서 받은 json 내용 복붙&lt;/b&gt;&lt;br /&gt;name: GCP_SA_KEY&lt;br /&gt;Secret: json내용 열어서 그대로 복붙&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 위에서 firebase연결하면서 저장한 google-service.json 내용 복붙&lt;/b&gt;&lt;br /&gt;Name: GOOGLE_SERVICES_JSON&amp;nbsp;&lt;br /&gt;Secret: google-service.json 내용 그대로 복붙&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. 서비스계정 키에서 확인&lt;/b&gt;&lt;br /&gt;위에서 만든 서비스계정에 보면 @다음에 서비스이름-123456 이런식으로 이름이 있었습니다.&lt;br /&gt;Name: 위 코드 가장 마지막줄에 secrets.이름맞추기 &amp;lt;&amp;lt; 이름맞추기를 수정해줍니다.&lt;br /&gt;Secret: 서비스이름-123456 으로 넣어줍니다. 아마 숫자다음에 .iam... 뭐시기들은 무시하면됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추가하면 이렇게 3가지가 추가됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjYnYF/dJMcagLa3TA/QY9shRQhaEwkHeNkAFZabk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjYnYF/dJMcagLa3TA/QY9shRQhaEwkHeNkAFZabk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjYnYF/dJMcagLa3TA/QY9shRQhaEwkHeNkAFZabk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjYnYF%2FdJMcagLa3TA%2FQY9shRQhaEwkHeNkAFZabk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;425&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;h3 data-ke-size=&quot;size23&quot;&gt;6. Action 에서 Automated Robo QA 돌리기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에 설정들을 모두 제대로 했다면 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;5,2,0&quot;&gt;[Run workflow]&amp;nbsp;&lt;/b&gt;라는 버튼이 있을텐데 run이라고 보이는 버튼을 클릭하고 좀 오래 기다리면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/umbUn/dJMcabXnKS5/GWTRlFB8kw1VsbFvnxeEc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/umbUn/dJMcabXnKS5/GWTRlFB8kw1VsbFvnxeEc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/umbUn/dJMcabXnKS5/GWTRlFB8kw1VsbFvnxeEc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FumbUn%2FdJMcabXnKS5%2FGWTRlFB8kw1VsbFvnxeEc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;397&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;533&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;size18&quot;&gt;프로젝트와 설정들이 제대로 되었는지 확인하며 성공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;h3 data-ke-size=&quot;size23&quot;&gt;7. Firebase Test Lab에서 자동으로 테스트한 결과 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Test Lab 탭을 찾아 들어가면 AI가 어떤 테스트를 수행했는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Main에 Brunch될때마다 이게 수행되는걸로 아는데 이거 많이 돌리면 돈나가서 제가 robo_test.yml에 자동수행 막아놓고 수동으로 돌리게 해놨으니 테스트할려면 깃허브 Action가서 알아서 눌러주면됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IDBnz/dJMcabQBTJT/4qD00G7yQ3eZjeH7lKpDo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IDBnz/dJMcabQBTJT/4qD00G7yQ3eZjeH7lKpDo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IDBnz/dJMcabQBTJT/4qD00G7yQ3eZjeH7lKpDo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIDBnz%2FdJMcabQBTJT%2F4qD00G7yQ3eZjeH7lKpDo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;632&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;632&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;size18&quot;&gt;히힛 자동으로 돌아간다~~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;근데 눌러볼거 많은데 다 하질않네... 생각보다 테스트 퀄은 떨어지는구나...&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/AI</category>
      <category>AI</category>
      <category>Firebase Test Lab</category>
      <category>자동QA</category>
      <category>자동화AI</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/119</guid>
      <comments>https://codingisland.tistory.com/119#entry119comment</comments>
      <pubDate>Fri, 6 Feb 2026 18:38:02 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AI로 노래가사 해석하는 앱개발 - 3 (마무리)</title>
      <link>https://codingisland.tistory.com/118</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;지난글 링크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codingisland.tistory.com/116&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codingisland.tistory.com/116&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769669690871&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] AI로 노래가사 해석하는 앱개발&quot; data-og-description=&quot;앱을 만들게 된 과정1. 앱을 왜 만들고 어떻게 시작했는가?앱을 만들게 된 이유동기: 일본어 공부가 하고 싶어짐 -&amp;gt; 평소 Jpop 많이 들음.영감: 유튜브 쇼츠 카지노 차무식이 팝송 500곡 외웠다길래 &quot; data-og-host=&quot;codingisland.tistory.com&quot; data-og-source-url=&quot;https://codingisland.tistory.com/116&quot; data-og-url=&quot;https://codingisland.tistory.com/116&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbZ0PF/dJMb8WeultL/Qt0WG5DWOtv7Fhb4oZQ8O0/img.jpg?width=648&amp;amp;height=1404&amp;amp;face=0_0_648_1404,https://scrap.kakaocdn.net/dn/cUJGin/dJMb8PGqUZD/wuw1vlOphkPM2W7SRWke2K/img.jpg?width=648&amp;amp;height=1404&amp;amp;face=0_0_648_1404,https://scrap.kakaocdn.net/dn/huQVW/dJMb9dHiMYu/MtSKUuGPo0kyFvBMm398v0/img.png?width=558&amp;amp;height=734&amp;amp;face=0_0_558_734&quot;&gt;&lt;a href=&quot;https://codingisland.tistory.com/116&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingisland.tistory.com/116&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbZ0PF/dJMb8WeultL/Qt0WG5DWOtv7Fhb4oZQ8O0/img.jpg?width=648&amp;amp;height=1404&amp;amp;face=0_0_648_1404,https://scrap.kakaocdn.net/dn/cUJGin/dJMb8PGqUZD/wuw1vlOphkPM2W7SRWke2K/img.jpg?width=648&amp;amp;height=1404&amp;amp;face=0_0_648_1404,https://scrap.kakaocdn.net/dn/huQVW/dJMb9dHiMYu/MtSKUuGPo0kyFvBMm398v0/img.png?width=558&amp;amp;height=734&amp;amp;face=0_0_558_734');&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] AI로 노래가사 해석하는 앱개발&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;앱을 만들게 된 과정1. 앱을 왜 만들고 어떻게 시작했는가?앱을 만들게 된 이유동기: 일본어 공부가 하고 싶어짐 -&amp;gt; 평소 Jpop 많이 들음.영감: 유튜브 쇼츠 카지노 차무식이 팝송 500곡 외웠다길래&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingisland.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 title=&quot;두번째 글&quot; href=&quot;https://codingisland.tistory.com/117&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codingisland.tistory.com/117&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769669688573&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] AI로 노래가사 해석하는 앱개발 - 2&quot; data-og-description=&quot;지난 글 요약개발 동기: J-Pop을 즐겨 듣다 노래를 통째로 외워 일본어를 마스터하고 싶다는 생각에 개발을 시작했다제작 방식: Flutter의 중첩 구조가 낯설어 **AI와 협업하는 '바이브 코딩'**으로 Fi&quot; data-og-host=&quot;codingisland.tistory.com&quot; data-og-source-url=&quot;https://codingisland.tistory.com/117&quot; data-og-url=&quot;https://codingisland.tistory.com/117&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/z0jRO/dJMb8VNp4Yg/hwtgckHBq5QiUuAvFxdQJ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bjTXZU/dJMb8UHJWoc/e5qGRfZSgm206vrBKl1A31/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/duZY54/dJMb8TB4izj/kr5UP7EveAJS3BK20Yq2zK/img.png?width=1001&amp;amp;height=836&amp;amp;face=0_0_1001_836&quot;&gt;&lt;a href=&quot;https://codingisland.tistory.com/117&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingisland.tistory.com/117&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/z0jRO/dJMb8VNp4Yg/hwtgckHBq5QiUuAvFxdQJ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bjTXZU/dJMb8UHJWoc/e5qGRfZSgm206vrBKl1A31/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/duZY54/dJMb8TB4izj/kr5UP7EveAJS3BK20Yq2zK/img.png?width=1001&amp;amp;height=836&amp;amp;face=0_0_1001_836');&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] AI로 노래가사 해석하는 앱개발 - 2&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지난 글 요약개발 동기: J-Pop을 즐겨 듣다 노래를 통째로 외워 일본어를 마스터하고 싶다는 생각에 개발을 시작했다제작 방식: Flutter의 중첩 구조가 낯설어 **AI와 협업하는 '바이브 코딩'**으로 Fi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingisland.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 title=&quot;깃허브 링크&quot; href=&quot;https://github.com/rudIsland/AI_japaness_Lyrics_study&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/rudIsland/AI_japaness_Lyrics_study&lt;/a&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;Kasi-AI 일본어로 '카시코이(賢い)'는 '똑똑하다'라는 뜻입니다. AI가 똑똑하게 가사를 풀어준다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발하며 터진 문제와 해결한 방안&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 노래 가사 불러오기: &quot;AI가 다 해줄 줄 알았지&quot;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size18&quot;&gt;나는 평생 프로그래밍만 해서 기획에는 전혀 아는게 없었다. 처음엔 그냥 유튜브 영상 제목만 AI한테 주면, 얘가 알아서 원곡 제목이랑 가수명을 찾고 가사까지 싹 긁어올 줄 알았다. 근데 AI한테 커버곡 영상을 주니까 커버한 사람을 원곡 가수로 착각하고 가사도 엉뚱한 걸 가져온다. 전혀 도움이 안됐다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;해결:&lt;/b&gt; 결국 가수명과 제목을 사용자가 직접 입력하게 만들었다. 그리고 &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;4,0,0&quot;&gt;Netease&lt;/b&gt;와 &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;4,0,0&quot;&gt;LrcLib&lt;/b&gt;라는 두 종류의 API를 섞어 썼다. 하나만 쓰면 못 가져올 때가 있는데, 2개를 같이 돌리니까 성공률이 확 올라갔다. 가사를 못가져온 일은 그 뒤로 없었다. 덕분에 가사 싱크(타임스탬프)까지 가져와서 실시간 강조 기능도 넣을 수 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVXloj/dJMcagRSVxO/zmpbQkwOosZJdxqqkqaXPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVXloj/dJMcagRSVxO/zmpbQkwOosZJdxqqkqaXPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVXloj/dJMcagRSVxO/zmpbQkwOosZJdxqqkqaXPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVXloj%2FdJMcagRSVxO%2FzmpbQkwOosZJdxqqkqaXPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;236&quot; height=&quot;499&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;571&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;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 2. 소형 플레이어 (Mini Player) 개발 포기 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size18&quot;&gt;유튜브 뮤직처럼 노래를 듣다가 화면을 나가도 하단에 작게 남아서 계속 재생되는 기능을 넣고 싶었다. 간지 나고 노래듣다가 흐름도 끊기지 않으니 말이다. 그런데 플러터의 위젯 트리 구조상 부모-자식 위젯 사이에 노래가 끊기지 않게 연결하는 게 생각보다 너무 빡셌다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;해결:&lt;/b&gt; 고민하다가 깨달았다. 내 앱의 목적은 '노래 감상'이 아니라 '가사 번역 공부'다. 주객전도가 되면 안 되겠다 싶어서 &lt;b data-index-in-node=&quot;70&quot; data-path-to-node=&quot;7,0,0&quot;&gt;과감히 포기&lt;/b&gt;했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. AI 해석 문제: &quot;컴퓨터는 시키는 것만 한다&quot;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;AI는 똑똑해 보이지만 결국 시키는 것만 하는 기계다. 프롬프트(명령어)를 대충 주면 지 멋대로 번역을 하거나 형식을 망가뜨린다. 자연스러우면서도 공부하기 좋게 번역 시키려고 이 모델 저 모델 써보면서 토큰만 엄청 날렸다. 그렇게 2~3일은 명령만 내리다 날 샜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;해결:&lt;/b&gt; &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;11,0,0&quot;&gt;AI는 AI로 맞대응했다.&lt;/b&gt; 다른 똑똑한 AI 모델한테 &quot;야, 얘한테 어떻게 지시해야 번역 잘하냐?&quot;라고 물어보고, 거기서 나온 프롬프트를 그대로 꽂아 넣었더니 훨씬 잘 한다.. 몇 번 시켜보니 완벽하게 잘하더라&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bgz5p/dJMcadOjiE9/lddYROhu2lS5fNrcIJBsO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bgz5p/dJMcadOjiE9/lddYROhu2lS5fNrcIJBsO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bgz5p/dJMcadOjiE9/lddYROhu2lS5fNrcIJBsO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBgz5p%2FdJMcadOjiE9%2FlddYROhu2lS5fNrcIJBsO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;280&quot; height=&quot;540&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;569&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;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. FirebaseDB 추가 및 로그인 기능 추가에 대한 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;원래는 로컬 DB인 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;14&quot;&gt;Drift&lt;/b&gt;만 쓰려고 했다. 친구가 &quot;로컬로 충분해&quot;라고 하기도 했고. 근데 만들다 보니 욕심이 생겼다. 구글 로그인도 넣고 싶고, 클라우드 동기화도 하고 싶어서 &lt;b data-index-in-node=&quot;101&quot; data-path-to-node=&quot;14&quot;&gt;Firebase&lt;/b&gt;를 추가하기로 했다. 여기서 지옥이 시작됐다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;1) DriftDB(로컬) 와 FirebaseDB(클라우드), 두 DB의 공존 전략 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로그인을 안하고 사용하는 사람과 로그인을 하고 사용하는 사람간에 권한차이를 둬야겠단 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 비로그인은 로컬DB만 사용 / 로그인사용자는 클라우드DB로 관리되게끔 하려했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Drift는 SQL이고 Firbase는 NoSQL이라서 같은 기능이지만 코드구현은 달라야 하기에 두 로직을 어떻게 나누고, 어떻게 접근시키지란 고민이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 과정에서 기존 로직이 꼬여서 하루는 앱을 못키기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;나는 Flutter가 자연어에 가까운 언어라는 것을 생각했다. 바로 &lt;u&gt;Implements(위임)을 활용&lt;/u&gt;하기로 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1769672224976&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class DatabaseRepository {
  /// ⚙️ 초기화
  Future&amp;lt;void&amp;gt; saveInitialize();

  ///   [Group: Get] 조회
  Future&amp;lt;AccountData?&amp;gt; getAccount(String uid);
  Stream&amp;lt;List&amp;lt;VideoInfo&amp;gt;&amp;gt; getFrequentlyVideos();
  Future&amp;lt;int&amp;gt; getTodayCompletedCount();
  Future&amp;lt;bool&amp;gt; isVideoCompletedToday(String youtubeVideoId);
  Future&amp;lt;List&amp;lt;VideoInfo&amp;gt;&amp;gt; getFavoriteVideos();
  Stream&amp;lt;List&amp;lt;StudiedVideoUI&amp;gt;&amp;gt; getStudiedVideosUI();
  Future&amp;lt;VideoInfo&amp;gt; getVideoFromYoutube(YoutubeVideo video);
  Future&amp;lt;List&amp;lt;Lyric&amp;gt;&amp;gt; getLyricsForVideo(String youtubeVideoId);
  Future&amp;lt;GlobalVideoInfo?&amp;gt; getGlobalVideo(String youtubeVideoId);

  ///   [Group: Save/Insert] 생성 및 저장
  Future&amp;lt;void&amp;gt; saveAccount(User user, String? deviceId);
  Future&amp;lt;void&amp;gt; saveFrequentlyVideo(VideoInfo video);
  Future&amp;lt;void&amp;gt; saveGlobalVideo(GlobalVideoInfo video);
  Future&amp;lt;void&amp;gt; saveStudyRecord(String youtubeVideoId);
  Future&amp;lt;void&amp;gt; insertLyricsData(String youtubeVideoId, List&amp;lt;Lyric&amp;gt; lyrics);
  Future&amp;lt;bool&amp;gt; getFavoriteStatus(String videoId);

  ///  ️ [Group: Update] 수정
  Future&amp;lt;void&amp;gt; updateFavoriteStatus(VideoInfo video, bool status);
  Future&amp;lt;void&amp;gt; updateAnalyzedLyrics(String youtubeVideoId, List&amp;lt;Lyric&amp;gt; lyrics);
  Future&amp;lt;void&amp;gt; updateLyricsData(String youtubeVideoId, List&amp;lt;Lyric&amp;gt; lyrics);
  Future&amp;lt;void&amp;gt; updateIsAnalyzed(String youtubeVideoId, bool status);
  Future&amp;lt;void&amp;gt; updateIsLyrics(String youtubeVideoId, bool status);
  Future&amp;lt;void&amp;gt; updateSearchedInfo({
    required String youtubeVideoId,
    required String searchedArtist,
    required String searchedTitle,
  });

  ///  ️ [Update] 특정 필드의 카운트를 1 올리고 날짜를 오늘로 기록
  Future&amp;lt;void&amp;gt; updateDailyCount(String fieldName);

  ///  ️ [Group: Delete] 삭제
  Future&amp;lt;void&amp;gt; deleteStudiedRecord(int recordId);
}&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;size18&quot;&gt;이렇게 추상 클래스로 뼈대를 만들고, Drift와 Firebase 클래스가 각각 이걸 구현하게 했다. 그리고 &lt;b data-index-in-node=&quot;61&quot; data-path-to-node=&quot;17&quot;&gt;ManagerDatabase&lt;/b&gt;라는 관리자 클래스를 만들어서 로그인 상태면 Firebase를, 아니면 Drift를 호출하게 위임시켰다. 덕분에 UI 코드에서는 로그인 여부를 일일이 따질 필요 없이 관리자 인스턴스만 부르면 되니까 훨씬 깔끔해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;912&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3dlgo/dJMcadnhrU9/uovnC3ZjK93d7QY8KliXg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3dlgo/dJMcadnhrU9/uovnC3ZjK93d7QY8KliXg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3dlgo/dJMcadnhrU9/uovnC3ZjK93d7QY8KliXg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3dlgo%2FdJMcadnhrU9%2FuovnC3ZjK93d7QY8KliXg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;912&quot; height=&quot;428&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 결과 &lt;u&gt;ManagerDB클래스의 싱글톤 instance에 접근해서 필요한 함수를 호출하면 로그인 여부는 관리자클래스가 알아서 해주니 헷갈릴 일 없이 쉽게 구현할 수 있었다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;2) 로그인 여부에 따른 권한 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아무래도 로그인을 했느냐 안했느냐에 권한을 줘야했다. 로그인을 하면 다른 기기에서 같은 아이디로 로그인 했을때 기록이 동기화되어야 했고, 할 수 있는 권한도 &lt;u&gt;차별화를 두고 싶었다.&lt;/u&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 210px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 21px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;기능 (Feature)&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;  비로그인 (Guest)&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;  로그인 (User)&lt;/td&gt;
&lt;td style=&quot;width: 24.7674%; height: 21px;&quot;&gt;비고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 84px;&quot; rowspan=&quot;3&quot;&gt;&lt;b&gt;핵심기능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;&lt;b&gt;AI 가사 분석&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;❌ 불가&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;⭕ &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;4,1,3,0&quot;&gt;사용 가능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.7674%; height: 21px;&quot;&gt;AI 토큰 비용 관리 목적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;&lt;b&gt;가사 검색(불러오기)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;❌ 불가&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;⭕ &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;4,1,3,0&quot;&gt;사용 가능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.7674%; height: 21px;&quot;&gt;정확한 가사 매칭 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 21.7442%; height: 42px;&quot;&gt;&lt;b&gt;유튜브 영상 재생&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 42px;&quot;&gt;⭕ 가능&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 42px;&quot;&gt;⭕ 가능&lt;/td&gt;
&lt;td style=&quot;width: 24.7674%; height: 42px;&quot;&gt;기본 학습 기능은 모두 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 84px;&quot; rowspan=&quot;4&quot;&gt;&lt;b&gt;데이터&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;&lt;b&gt;저장위치&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;내 휴대폰&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;클라우드 서버&lt;/td&gt;
&lt;td style=&quot;width: 24.7674%; height: 21px;&quot; rowspan=&quot;5&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;&lt;b&gt;공부기록/즐겨찾기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;기기에만 저장&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;계정에 저장(동기화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;&lt;b&gt;데이터 보존&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;앱 삭제시 &lt;b&gt;초기화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;앱 삭제해도 &lt;b&gt;유지&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;&lt;b&gt;기기연동&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;불가(기기 변경시 끝)&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;가능(다른 폰 연동가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 21px;&quot;&gt;기타&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 21px;&quot;&gt;자주 듣는 곡 / 공부기록&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%; height: 21px;&quot;&gt;로컬 기록 기반&lt;/td&gt;
&lt;td style=&quot;width: 21.2791%; height: 21px;&quot;&gt;서버 기록 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;권한은 이렇게 부여했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;핵심은 &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;20&quot;&gt;클라우드 집단지성&lt;/b&gt;이다. 로그인 유저가 AI로 가사를 한 번 분석해서 클라우드에 올려두면, 나중에 비로그인 유저가 그 노래를 들을 때 그 정보를 가져다 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1) 비로그인 유저가 노래A를 원함. -&amp;gt; 클라우드에는 노래A가 없어서 가사 정보도 없음. -&amp;gt; 노래는 들을 수 있지만 가사는 못봄&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2) -&amp;gt; 로그인유저가 노래A를 원함 -&amp;gt; AI와 가사불러오기를 활용해 가사 정보를 불러와 클라우드에 저장됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3) -&amp;gt; 비로그인유저가 노래A를 다시 들으러 클릭함. -&amp;gt; 클라우드에 가사 정보가 있으니 가사를 볼 수 있음. -&amp;gt; 가사 정보를 로컬에 저장. -&amp;gt; 나중에 다시 들어와도 볼 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 되면 로컬(Drift)유저도 Firebase에 접근이 필요하게 되긴한다. 하지만 이런 기능을 꼭 구현하고 싶었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 코드의 구조와 성능: 바이브 코딩의 한계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;AI가 짜주는 대로 갖다 붙이다 보니 성능은 뒷전이었다. 근데 테스트를 해보니 동기화가 묘하게 느리더라. 범인은 &lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;23&quot;&gt;Stream&lt;/b&gt; 남발과 &lt;b data-index-in-node=&quot;74&quot; data-path-to-node=&quot;23&quot;&gt;StatefulWidget&lt;/b&gt;의 남용이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;Future vs Stream:&lt;/b&gt; 유니티로 치면 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;24,0,0&quot;&gt;Awake와 Update&lt;/b&gt; 차이다. 1회성 데이터는 Future면 충분한데 무지성으로 Stream을 꽂아놔서 데이터가 꼬였던 것이다. 호출 시점에만 딱 부르는 Future로 바꾸니 깔끔해졌다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,1,0&quot;&gt;Stateful 위젯:&lt;/b&gt; 플러터는 위젯 단위로 다시 그린다. 근데 화면 전체를 Stateful로 감싸버리니 텍스트 하나 바뀔 때 화면 전체를 다시 그리는 비효율이 발생했다. 변화가 필요한 부분만 쏙 빼서 Stateful로 만들고 나머지는 Stateless로 박아버리는 게 성능의 핵심이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&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;383&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BfcNg/dJMcac2X6AG/wXE3fzZofAwDo1ID4u9RK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BfcNg/dJMcac2X6AG/wXE3fzZofAwDo1ID4u9RK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BfcNg/dJMcac2X6AG/wXE3fzZofAwDo1ID4u9RK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBfcNg%2FdJMcac2X6AG%2FwXE3fzZofAwDo1ID4u9RK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;753&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;초록 테두리를 Stateless로 두어 변하지 않게 두었고, 그 화면 내에 변화가 필요한 위젯들인 빨간테두리에만 Stateful을 하여 구현해야했다. 하지만 나는 홈-기록-즐겨찾기 탭을 슬라이드로 넘기는 방식을 구현하고자 Stateful을 해야했다. 그래도 앱의 규모가 작아서 속도로 문제되진 않았다. 하지만 앞으로의 Flutter에서는 필요한 곳만 Stateful을 사용해야 할 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발을 마친 후 앱 화면&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwFjdi/dJMcaihQ3u2/oU17YEK1sQe6yf48F7yKM0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwFjdi/dJMcaihQ3u2/oU17YEK1sQe6yf48F7yKM0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwFjdi/dJMcaihQ3u2/oU17YEK1sQe6yf48F7yKM0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwFjdi%2FdJMcaihQ3u2%2FoU17YEK1sQe6yf48F7yKM0%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;238&quot; height=&quot;506&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;752&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSPDSc/dJMcaac3Mz9/G3AQfKFTrWl0mcyMJGjwnK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSPDSc/dJMcaac3Mz9/G3AQfKFTrWl0mcyMJGjwnK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSPDSc/dJMcaac3Mz9/G3AQfKFTrWl0mcyMJGjwnK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSPDSc%2FdJMcaac3Mz9%2FG3AQfKFTrWl0mcyMJGjwnK%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;288&quot; height=&quot;608&quot; data-origin-width=&quot;349&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QVIRQ/dJMcagj2ntu/X0EMd6jarkw3lryBIU70w0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QVIRQ/dJMcagj2ntu/X0EMd6jarkw3lryBIU70w0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QVIRQ/dJMcagj2ntu/X0EMd6jarkw3lryBIU70w0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQVIRQ%2FdJMcagj2ntu%2FX0EMd6jarkw3lryBIU70w0%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;289&quot; height=&quot;606&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;746&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LS4wT/dJMcacBWXMt/AKtXPFqZ6slq8dUFp0OIA1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LS4wT/dJMcacBWXMt/AKtXPFqZ6slq8dUFp0OIA1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LS4wT/dJMcacBWXMt/AKtXPFqZ6slq8dUFp0OIA1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLS4wT%2FdJMcacBWXMt%2FAKtXPFqZ6slq8dUFp0OIA1%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;307&quot; height=&quot;641&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;739&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zds8O/dJMcacBWXPo/VOpKKioGqfMeomIGFvPzIk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zds8O/dJMcacBWXPo/VOpKKioGqfMeomIGFvPzIk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zds8O/dJMcacBWXPo/VOpKKioGqfMeomIGFvPzIk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZds8O%2FdJMcacBWXPo%2FVOpKKioGqfMeomIGFvPzIk%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;298&quot; height=&quot;613&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;741&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8HyBI/dJMcafen8lm/JrOYpHooBTkaYkhARmRLJK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8HyBI/dJMcafen8lm/JrOYpHooBTkaYkhARmRLJK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8HyBI/dJMcafen8lm/JrOYpHooBTkaYkhARmRLJK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8HyBI%2FdJMcafen8lm%2FJrOYpHooBTkaYkhARmRLJK%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;301&quot; height=&quot;632&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;741&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리 소감&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무언가를 만들어낸다는 감각은 언제나 재밌고 짜릿하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그게 내 순수 능력으로 만드는 것은 아닐지라도, 내 능력보다 더 엄청난 것을 만들어 줄 수 있게 해주는게 &quot;AI&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;나는 그 AI를 활용하는 능력을 나쁘게 보진 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 1. &quot;AI가 다 해준 거 아냐?&quot;라는 시선에 대해&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 당장 무언가를 완성하고 싶은 열망에 비해 내 능력치가 벅찰 때가 있고, 무엇보다 AI의 발전 속도는 내 성장 속도보다 훨씬 빠르다. 나는 이 흐름을 거부하기보다 &lt;b data-index-in-node=&quot;142&quot; data-path-to-node=&quot;3&quot;&gt;적극적으로 올라타기로 했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번 개발을 통해 느낀 건, &lt;b data-index-in-node=&quot;174&quot; data-path-to-node=&quot;3&quot;&gt;AI가 앱을 완성시킬 수 있는 비중은 딱 60% 정도&lt;/b&gt;라는 것이다. 나머지 40%는 결국 사람이 채워야 한다. 내가 기초적인 Dart 문법을 알고, 유니티(C#)를 통해 객체지향 개념을 잡고 있었기에 이 앱도 완성될 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 2. 왜 여전히 '개발자'가 필요한가&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size18&quot;&gt;AI는 편리하지만 치명적인 단점이 있다. 앱이 커지면 AI는 이전에 짰던 로직들을 하나둘 잊어버리거나 배제하기 시작하고, 결국 거기서 &lt;b data-index-in-node=&quot;79&quot; data-path-to-node=&quot;5&quot;&gt;심각한 버그&lt;/b&gt;가 터진다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size18&quot;&gt;기획서의 의도에 맞게 전체 클래스를 설계하고, 유지보수가 가능한 구조를 짜는 건 여전히 사람의 몫이다. 이번 프로젝트에서 내가 직접 한 일들도 바로 그런 것들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;아키텍처 설계:&lt;/b&gt; 로그인 여부에 따라 DB를 갈아끼우는 Repository 패턴 도입&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;코드 가독성 리팩토링:&lt;/b&gt; AI가 뭉뚱그려 짠 코드를 위젯과 함수 단위로 분리&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;버그 체커:&lt;/b&gt; AI가 불필요하게 구현하거나 놓친 예외 상황들을 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;위젯이 뭔지, 추상 클래스가 뭔지 모르는 상태에서 AI에게 떠맡기기만 했다면, 아마 이 앱은 중간에 스파게티 코드가 되어 터져버렸을 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 반성: 구조에 대한 아쉬움&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;나름 유지보수를 생각해서 Database, Screen, Widget, Service 같이 카테고리별로 폴더를 나누어 배치했다. 그런데 파일이 많아지다 보니 정작 필요한 파일을 찾기가 점점 힘들어졌다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size18&quot;&gt;예전에 Spring 프로젝트를 할 때는 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;12&quot;&gt;엔티티(Entity)별&lt;/b&gt;로 나눠서 관리하는 게 편했는데, Flutter는 &lt;b data-index-in-node=&quot;62&quot; data-path-to-node=&quot;12&quot;&gt;기능(Feature)이나 화면(Screen)별&lt;/b&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-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19&quot;&gt;  이번 시리즈의 핵심 요약:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;기획의 본질:&lt;/b&gt; AI는 도구일 뿐, 목적지는 사람이 정해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;기술의 융합:&lt;/b&gt; 유니티의 생명주기 개념이 플러터 이해에 큰 도움이 됐다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;구조의 중요성:&lt;/b&gt; 바이브 코딩을 하더라도 '유지보수'를 위한 아키텍처는 필수다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 매우 만족스러운 개발이였고, 취업하고싶다..&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>AI</category>
      <category>Flutter</category>
      <category>앱개발</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/118</guid>
      <comments>https://codingisland.tistory.com/118#entry118comment</comments>
      <pubDate>Thu, 29 Jan 2026 18:29:59 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AI로 노래가사 해석하는 앱개발 - 2</title>
      <link>https://codingisland.tistory.com/117</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;지난 글 요약&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;개발 동기&lt;/b&gt;: J-Pop을 즐겨 듣다 노래를 통째로 외워 일본어를 마스터하고 싶다는 생각에 개발을 시작했다&lt;br /&gt;&lt;b&gt;제작 방식&lt;/b&gt;: Flutter의 중첩 구조가 낯설어 **AI와 협업하는 '바이브 코딩'**으로 Figma 디자인부터 기능 구현까지 신속하게 진행했다.&lt;br /&gt;&lt;b&gt;기술 스택&lt;/b&gt;: YouTube API와 가사 전문 API(Netease, LrcLib)를 조합해 정확도를 높였고, Gemini로 가사 해석을 처리했다.&lt;br /&gt;&lt;b&gt;문제 해결&lt;/b&gt;: AI의 오역과 싱크 불일치 문제를 집요한 프롬프트 수정과 외부 데이터 연동을 통해 해결하며 완성도를 높였다.&lt;br /&gt;&lt;b&gt;최종 결과&lt;/b&gt;: 약 6일간의 집중 개발 끝에 탄생한 순수 개인 공부용 앱으로, 열정만으로 기획부터 결과물까지 만들어냈다.&lt;/blockquote&gt;
&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 title=&quot;[Flutter] AI로 노래가사 해석하는 앱개발&quot; href=&quot;https://codingisland.tistory.com/116&quot; rel=&quot;noopener&quot;&gt;https://codingisland.tistory.com/116&lt;/a&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;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2번째 글을 쓰게 된 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;앱을 만들다 보니 조금씩 더 욕심이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기능이 한 두개 더 추가됐으면 좋겠다는 생각이 들어 추가로 구현하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;단지 그 뿐, 그저 개발이 재밌다.. &lt;s&gt;코드 성능은 모르겠다&lt;/s&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지금까지 넣은 기술 요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- YoutubeAPI: 유튜브 영상을 가져와 노래인 영상들을 가져오기 위함. + 영상 플레이어로 노래 듣기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Netease + LrcLib: 노래 가사를 가져오기 위해 Http 통신으로 소스코드에 있는 가사와 타임스탬프를 가져옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Gemini API: 가져온 가사를 일본어로 해석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Drift: 로컬DB용으로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;난 여기서 계정 로그인 기능을 추가하여, &lt;b&gt;오프라인일때는 로컬DB&lt;/b&gt;를, &lt;b&gt;온라인일 때는 클라우드 DB&lt;/b&gt;의 정보를 가져오고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;유튜브에서 보면 오프라인일때는 로컬의 알고리즘으로 영상추천이 나오고, 로그인하면 어느 기기든 동일한 알고리즘이 나오는 것을 보고, 나도 그렇게 만들고싶었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가한 기술&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Firebase&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;FireBase는 구글 계정 로그인도 API로 지원해준다. 마침 Gemini와 Youtube API를 사용중이던 나에게 맞는 기술이였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qY1Lv/dJMcabQwIzs/t1r9LSuLNbjtBJwWgw8uMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qY1Lv/dJMcabQwIzs/t1r9LSuLNbjtBJwWgw8uMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qY1Lv/dJMcabQwIzs/t1r9LSuLNbjtBJwWgw8uMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqY1Lv%2FdJMcabQwIzs%2Ft1r9LSuLNbjtBJwWgw8uMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;561&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 연결하려고 프로젝트 폴더에서 android에 build.gradle에 뭘 자꾸 추가해야된다... 그랬지만 우리 킹갓 Gemini님께서 다 설명해주신다. 그래서 금방 연결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;구글 계정 연동&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuqIOa/dJMcaajM05g/8ko8GiHjsmGds7KVKbmLK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuqIOa/dJMcaajM05g/8ko8GiHjsmGds7KVKbmLK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuqIOa/dJMcaajM05g/8ko8GiHjsmGds7KVKbmLK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuqIOa%2FdJMcaajM05g%2F8ko8GiHjsmGds7KVKbmLK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;267&quot; height=&quot;561&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;708&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;333&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brVYV3/dJMb99LWl3R/osVUJdPRSPbNOCUuWOV1jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brVYV3/dJMb99LWl3R/osVUJdPRSPbNOCUuWOV1jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brVYV3/dJMb99LWl3R/osVUJdPRSPbNOCUuWOV1jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrVYV3%2FdJMb99LWl3R%2FosVUJdPRSPbNOCUuWOV1jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;471&quot; data-origin-width=&quot;333&quot; data-origin-height=&quot;625&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;팁 메모장 추가&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4z4yM/dJMcafMbvSA/DQt77HmUilqTCuJ8b1OGW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4z4yM/dJMcafMbvSA/DQt77HmUilqTCuJ8b1OGW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4z4yM/dJMcafMbvSA/DQt77HmUilqTCuJ8b1OGW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4z4yM%2FdJMcafMbvSA%2FDQt77HmUilqTCuJ8b1OGW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;335&quot; height=&quot;674&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;674&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;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앞으로 해야할 작업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;영상으로는 계정들이 나와서 보여주진 못하지만 전 글에 AI가 번역하고나서 일본어랑 한국어가 섞여 나오는 경우가 있었길래 그 부분도 프롬프트를 수정해서 이제는 잘 가져오고있다. 하지만 DB와 API호출로 수정해야할 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. DB문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;지금은 앱을 완성시키려고만 막 만든 형태다. 그러다 보니 DB의 정규화라던가 문제가 있다. Firebase가 Nosql이라서 DB로직을 수정하느라 하루 애먹긴했었다. Repository클래스를 만들어 Drift와 Firebase에서 수행할 공통에 로직들을 정의해 위임시켜 로그인 여부로 어느 DB에서 sql을 수행할지 나눈상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. API 호출문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;한 번 가사를 호출하고 번역했으면 이미 DB에 그 정보가 있기에 나중에 또다시 생성하지 않아도 된다. 하지만, 다른 사람이 같은 노래를 번역시키게 되면 각각 API호출로 번역시키는 상태이다. 이를 DB에 공통정보를 담을 데이터를 저장시켜 이미 노래를 해석해둔게 있다면 API를 호출하지않고 DB에서 불러오게 할 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;A씨와 B씨가 앱을 사용한다. 노래1과 노래2가 DB에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;A씨는 노래3을 원한다. -&amp;gt; API호출로 노래를 번역하고 공통의 클라우드 DB에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;후에 B씨가 노래3을 원한다 -&amp;gt; 전에 저장해뒀던 DB정보를 불러와 그대로 보여준다. (API 호출X)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 수정하기 위해 DB를 고쳐야한다.&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>Flutter</category>
      <category>앱개발</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/117</guid>
      <comments>https://codingisland.tistory.com/117#entry117comment</comments>
      <pubDate>Sun, 25 Jan 2026 17:47:44 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AI로 노래가사 해석하는 앱개발</title>
      <link>https://codingisland.tistory.com/116</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱을 만들게 된 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 앱을 왜 만들고 어떻게 시작했는가?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;앱을 만들게 된 이유&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;동기&lt;/b&gt;: 일본어 공부가 하고 싶어짐 -&amp;gt; 평소 Jpop 많이 들음.&lt;br /&gt;&lt;b&gt;영감&lt;/b&gt;: 유튜브 쇼츠 카지노 차무식이 팝송 500곡 외웠다길래 나도? 싶음.&lt;br /&gt;&lt;b&gt;결론&lt;/b&gt;: 500곡까진 아니어도 노래 통째로 외워보려는데 일일이 찾기 귀찮음 -&amp;gt; &quot;앱 하나 만들어서 AI한테 번역시키고 편하게 보자!&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;평소 C#으로 Unity 개발을 해왔기에 언어는 익숙한데... 디자인을 꾸미는게 html같아서 너무 헷갈린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위젯안에 위젯안에 위젯안에...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;디자인도 제대로 못하고 기획도 제대로 못하고 해석도 제대로 못하는데 처음부터 끝까지 AI한테 시키자 싶어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;내가 직접 한건 예시화면 그리기, 기능 설명 밖에 없다. 그저 바이브코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 Figma로 예시화면을 그렸다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Figma 예시화면 그리기&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dO9SDm/dJMcacBR53q/a6oEIH8TnanYEopkz6OoWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dO9SDm/dJMcacBR53q/a6oEIH8TnanYEopkz6OoWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dO9SDm/dJMcacBR53q/a6oEIH8TnanYEopkz6OoWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdO9SDm%2FdJMcacBR53q%2Fa6oEIH8TnanYEopkz6OoWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;558&quot; height=&quot;734&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;피그마를 처음써본건 아닌데 그렇다고 제대로 써본적도 없다. 그냥 손만 대봤다 수준이였어서 대충 막그렸다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;평소 Youtube Music 앱을 자주써서 복붙수준으로 그렸다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;근데 Figma도 AI기능이 있고 알아서 그려준다네? 그래서 AI한테 디자인을 시켰다.&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;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be48PN/dJMcac9EqNF/1hHFpIdbdhveukwNEitjZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be48PN/dJMcac9EqNF/1hHFpIdbdhveukwNEitjZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be48PN/dJMcac9EqNF/1hHFpIdbdhveukwNEitjZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe48PN%2FdJMcac9EqNF%2F1hHFpIdbdhveukwNEitjZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;195&quot; height=&quot;453&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;749&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqKh5V/dJMcaioxbwu/Es97sL6KfG9YHdOoDIzYXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqKh5V/dJMcaioxbwu/Es97sL6KfG9YHdOoDIzYXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqKh5V/dJMcaioxbwu/Es97sL6KfG9YHdOoDIzYXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqKh5V%2FdJMcaioxbwu%2FEs97sL6KfG9YHdOoDIzYXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;525&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;803&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 만들어 주는게 아닌가 너무 예쁘게 잘 만들어줬다. 그래서 가사를 볼땐 이 화면을 많이 따라하려 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;213&quot; data-origin-height=&quot;679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clAKfb/dJMcabwbxs8/TvXoiRhKjuuGkBzqTknfsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clAKfb/dJMcabwbxs8/TvXoiRhKjuuGkBzqTknfsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clAKfb/dJMcabwbxs8/TvXoiRhKjuuGkBzqTknfsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclAKfb%2FdJMcabwbxs8%2FTvXoiRhKjuuGkBzqTknfsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;165&quot; height=&quot;526&quot; data-origin-width=&quot;213&quot; data-origin-height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Flutter는 모두 Widget으로 이루어져있다.. 라고만 알고 싹다 AI한테 시켰다. 내가 한 것이라곤 나온 코드를 보고 유지보수나 가독성이 안좋은 네이밍 코드들은 직접 바꾼 것 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AI가 로컬DB로 Drift를 소개해주길래 연결도 해줬고, 위젯도 알아서 만들어주고 얼마나 편한가&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;대신 지멋대로 바꾸거나 없애거나 하는게 꽤 있어서 조금씩 울컥했지만 내가 할 수 있는건 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;초기 홈 화면&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PkShP/dJMcaihMFbr/qIOvAvN7C1tFTTNAaBwkKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PkShP/dJMcaihMFbr/qIOvAvN7C1tFTTNAaBwkKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PkShP/dJMcaihMFbr/qIOvAvN7C1tFTTNAaBwkKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPkShP%2FdJMcaihMFbr%2FqIOvAvN7C1tFTTNAaBwkKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;680&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;초기 노래 검색 화면&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CZYYu/dJMcaihMFbw/xGtYNEhtBzF6OmmMdbdK3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CZYYu/dJMcaihMFbw/xGtYNEhtBzF6OmmMdbdK3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CZYYu/dJMcaihMFbw/xGtYNEhtBzF6OmmMdbdK3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCZYYu%2FdJMcaihMFbw%2FxGtYNEhtBzF6OmmMdbdK3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;333&quot; height=&quot;722&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&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;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 사용한 기술들&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사용한 기술 목록&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;로컬 DB (Drift): AI가 소개해주길래 연결함.&lt;br /&gt;AI (Gemini 2.5 Flash-lite): 결제한 게 이거라 선택지 없었음. 지시만 똑바로 하면 번역 나쁘지 않음.&lt;br /&gt;네트워크 (Http): 가사 사이트 소스 긁어오려고 씀.&lt;br /&gt;가사 수집 (Netease + LrcLib): 아시아권 가사 제일 많고 싱크까지 줘서 선택함. 유튜브 API만으로는 가사 안 나오길래 이거 2개로 정확도 높임.&lt;br /&gt;미디어 (Youtube API): 썸네일, 영상 정보 가져오기 부족함이 없음.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 이렇게 썼는가 Why?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Youtube API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;처음 노래를 어디서 가져올지 찾아봤다. 난 국내음악도 일본어로 보고싶고, Jpop들도 필요했기에 아시아권에서 주는 API나 세계적인 음원사이트가 유리하겠구나 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;멜론이나 지니를 쓰고싶었는데 공개적으로 주는 api는 없었다.. 그래서 자주쓰는 유튜브API를 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;유튜브 API는 관련검색어, 썸네일이미지, 노래관련 정보들 등 많은 것을 주기에 부족함이 없었다. 초반에는 좋다 생각했다.. 하지만 그 뒤가 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Netease + LrcLib&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;난 처음에 가사도 AI가 유튜브 링크를 전달해서 영상에 자막이나 음성을 통해 알아서 가져와주는줄 알았다. 근데 안되더라? 그래서 영상 제목과 채널명을 가지고 가사를 제공해주는 외부 API를 써야겠다 생각이 들었다. 그래서 찾아본 곳이 이 두 곳이였다. 심지어 가사 싱크도 맞출 수 있어서 굉장히 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;처음에는 Genius도 했었는데 하다보니 저 2개면 충분히 가사를 가져오기에 모자람이 없기에 내동댕이쳤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DhxmD/dJMcahQH4On/z1qgL5c0lPC8UEaqQAlf1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DhxmD/dJMcahQH4On/z1qgL5c0lPC8UEaqQAlf1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DhxmD/dJMcahQH4On/z1qgL5c0lPC8UEaqQAlf1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDhxmD%2FdJMcahQH4On%2Fz1qgL5c0lPC8UEaqQAlf1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;199&quot; height=&quot;415&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Http&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;얘는 Netease와 LrcLib가 API Key를 주지 않고 Http로 쏘면 그 소스코드에서 가져오는거 같았다. 그래서 얘가 필요하단다 그러고 json형식에 맞추서 가져오게 시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Gemini AI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;최근 결제한 AI가 얘라서 그냥 선택지가 없었다. 그리고 얘가 번역도 나쁘지 않게 하더라 지시만 똑바로 하면.&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;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 하면서 터진 문제들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 가사 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에도 적었지만 영상만 가져오면 AI가 알아서 가사들을 가져와주는 줄 알았다... 그게 아니더라 다른 API 찾고 제대로 못가져오면 다른곳 찾고 하느라 3시간은 썼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 한 곳만 하기엔 가져오는 정확도가 낮았다. 그래서 Netease와 LrcLib 2개로 원곡의 가사를 가져올 정확도를 높였다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YUY88/dJMcab31mjf/2m0wyj4P2nCqzKIHQ78sP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YUY88/dJMcab31mjf/2m0wyj4P2nCqzKIHQ78sP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YUY88/dJMcab31mjf/2m0wyj4P2nCqzKIHQ78sP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYUY88%2FdJMcab31mjf%2F2m0wyj4P2nCqzKIHQ78sP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;204&quot; height=&quot;442&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;잘 가져와 주시잖아..한잔해&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 소형 플레이어?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Youtube Music을 따라 만들려다보니 기존 노래를 나가면 노래가 끊기는게 싫어서 밑에 소형 플레이어가 생기게 하고 노래가 안끊기게 하고싶었다. 그렇게 코드에 끼워넣다보니 자꾸 예외가 터지면서 가사 해석이라는 앱의 목적에서 멀어지려는게 아닌가.. 그래서 이건 과감히 제거했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. AI 해석문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;유튜브 API다보니 커버곡들이 너무 많았다. 그래도 원곡의 가사를 가져올 수 있어서 그건 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 얘가 가사를 자기 멋대로 해석하거나 바꿔버리는게 많았다. 그래서 가져올때 마다 문제점들을 계속 추가하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그거하지마. 이거하지마. 이렇게 해. 프롬프트가 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1067&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGT0f7/dJMcacIDXwJ/CSxMH2YxxL2JoyVnQh4bi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGT0f7/dJMcacIDXwJ/CSxMH2YxxL2JoyVnQh4bi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGT0f7/dJMcacIDXwJ/CSxMH2YxxL2JoyVnQh4bi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGT0f7%2FdJMcacIDXwJ%2FCSxMH2YxxL2JoyVnQh4bi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;243&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;243&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결과 화면들&lt;/h3&gt;
&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-filename=&quot;KakaoTalk_20260118_154003985_02.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ocrRp/dJMcabJKblL/7G3zdPCqkk1iDYmrMX0bS1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ocrRp/dJMcabJKblL/7G3zdPCqkk1iDYmrMX0bS1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ocrRp/dJMcabJKblL/7G3zdPCqkk1iDYmrMX0bS1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FocrRp%2FdJMcabJKblL%2F7G3zdPCqkk1iDYmrMX0bS1%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;214&quot; height=&quot;464&quot; data-filename=&quot;KakaoTalk_20260118_154003985_02.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20260118_154003985_01.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o3Zjf/dJMcadtW8DD/13jx4QvGtknfgbi3DkKIFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o3Zjf/dJMcadtW8DD/13jx4QvGtknfgbi3DkKIFK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o3Zjf/dJMcadtW8DD/13jx4QvGtknfgbi3DkKIFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo3Zjf%2FdJMcadtW8DD%2F13jx4QvGtknfgbi3DkKIFK%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;219&quot; height=&quot;474&quot; data-filename=&quot;KakaoTalk_20260118_154003985_01.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&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;즐겨찾기 탭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20260118_154003985.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/spDJm/dJMcac9ErcI/vbKFpeA3kkbUycsslKXEN0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/spDJm/dJMcac9ErcI/vbKFpeA3kkbUycsslKXEN0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/spDJm/dJMcac9ErcI/vbKFpeA3kkbUycsslKXEN0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FspDJm%2FdJMcac9ErcI%2FvbKFpeA3kkbUycsslKXEN0%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;214&quot; height=&quot;464&quot; data-filename=&quot;KakaoTalk_20260118_154003985.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&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-filename=&quot;KakaoTalk_20260118_154003985_03.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcbUGM/dJMcad1M33Q/e4TYZ0Q8DVD6Rz3DFRYYdk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcbUGM/dJMcad1M33Q/e4TYZ0Q8DVD6Rz3DFRYYdk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcbUGM/dJMcad1M33Q/e4TYZ0Q8DVD6Rz3DFRYYdk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcbUGM%2FdJMcad1M33Q%2Fe4TYZ0Q8DVD6Rz3DFRYYdk%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;209&quot; height=&quot;453&quot; data-filename=&quot;KakaoTalk_20260118_154003985_03.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;/figure&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-filename=&quot;KakaoTalk_20260118_154003985_04.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLpbsz/dJMcabiE9Wj/26I8VinwRBkfqyD2c6fgQ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLpbsz/dJMcabiE9Wj/26I8VinwRBkfqyD2c6fgQ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLpbsz/dJMcabiE9Wj/26I8VinwRBkfqyD2c6fgQ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLpbsz%2FdJMcabiE9Wj%2F26I8VinwRBkfqyD2c6fgQ0%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;201&quot; height=&quot;435&quot; data-filename=&quot;KakaoTalk_20260118_154003985_04.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&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;앱 개발까지 기간으로는 7일 일수로는 6일 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026.01.12(월) ~ 2026.01.18(일) 수요일은 쉼.&lt;/p&gt;
&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;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;
        </description>
      <category>프로젝트</category>
      <category>Flutter</category>
      <category>앱개발</category>
      <category>하고 싶어서</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/116</guid>
      <comments>https://codingisland.tistory.com/116#entry116comment</comments>
      <pubDate>Sun, 18 Jan 2026 15:52:40 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 구조] 컴퓨터 구조의 큰그림</title>
      <link>https://codingisland.tistory.com/115</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컴퓨터가 이해하는 정보&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;컴퓨터는 작성한 소스코드를 &lt;span style=&quot;color: #ee2323;&quot;&gt;데이터&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323;&quot;&gt;명령어&lt;/span&gt; 형태로 변환 뒤 실행&lt;br /&gt;&lt;br /&gt;&lt;b&gt;명령어&lt;/b&gt;: 컴퓨터에게 &lt;u&gt;행동 지시&lt;/u&gt;를 내리는 말&lt;br /&gt;&lt;b&gt;데이터&lt;/b&gt;: 명령의 &lt;u&gt;대상&lt;/u&gt;, 명령어의 &lt;u&gt;재료&lt;/u&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ CPU의 종류에 따라 실행가능한 명령어의 종류나 처리방식이 달라진다.&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;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컴퓨터의 핵심부품&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CPU&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;h2 data-ke-size=&quot;size26&quot;&gt;CPU&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;CPU&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;명령어를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;읽고&lt;/b&gt; &lt;b&gt;해석&lt;/b&gt;하고 &lt;b&gt;실행&lt;/b&gt;&lt;/span&gt;하는 부품&lt;/blockquote&gt;
&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;b&gt;ALU(산술연산장치)&lt;/b&gt;: 연산을 &lt;u&gt;수행하는 회로로 구성&lt;/u&gt;된 계산기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;CU(제어장치)&lt;/b&gt;: 명령어를 &lt;u&gt;해석해 전기신호로 내보내는&lt;/u&gt; 장치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 전기신호: 부&lt;u&gt;품을 작동시키기 위한 신호&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;레지스터&lt;/b&gt;: 명령어를 &lt;u&gt;처리하며 중간값을 저장&lt;/u&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;h2 data-ke-size=&quot;size26&quot;&gt;주기억 장치&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;주기억장치&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;&lt;b&gt;실행중&lt;/b&gt;&quot;인 프로그램의 데이터와 명령어를 저장&lt;/span&gt;하는 부품으로 &lt;b&gt;휘발성&lt;/b&gt;이다.&lt;br /&gt;RAM과 ROM이 주기억 장치이다.&lt;/blockquote&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ &lt;b&gt;캐시메모리&lt;/b&gt;: CPU가 조금이라도 빠르게 메모리에 저장된 값을 접근하기 위해 사용하는 저장장치. 빠른 메모리 접근을 보조&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;보조기억 장치&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;보조기억장치&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주기억장치를 보조&lt;/b&gt;하기 위한 장치&lt;/span&gt;로, &lt;b&gt;비휘발성&lt;/b&gt; 저장장치이다. 보관할 프로그램을 저장한다.&lt;br /&gt;&lt;br /&gt;ex) CD-ROM, 하드디스크, 플래시메모리(SSD, USB메모리), 플로피 디스크&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비휘발성은 &lt;u&gt;전원이 꺼져도 정보가 사라지지 않는 것&lt;/u&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;※ CPU는 보조기억장치에 저장된 프로그램을 &lt;u&gt;곧장 가져와 실행할 수 없기 때문&lt;/u&gt;에 보조기억장치에 있는 프로그램을 &lt;u&gt;주기억장치로 복사&lt;/u&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 장치&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;입출력장치&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;컴퓨터 &lt;span style=&quot;color: #ee2323;&quot;&gt;외부에 연결&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;되어 &lt;b&gt;내부와 정보를 교환&lt;/b&gt;&lt;/span&gt;하는 장치, 주변장치라고 통칭하기도 한다.&lt;br /&gt;&lt;br /&gt;입력장치: 마우스, 키보드&lt;br /&gt;출력장치: 모니터, 프린터, 스피커&lt;/blockquote&gt;
&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;/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;u&gt;&lt;b&gt;정보를 교환하기 위한 통로&lt;/b&gt;&lt;/u&gt;를 &lt;b&gt;버스&lt;/b&gt;라고 부르며, &lt;u&gt;&lt;b&gt;핵심 부품들을 연결&lt;/b&gt;&lt;/u&gt;하는 &lt;b&gt;시스템 버스&lt;/b&gt;가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>컴퓨터과학(CS)</category>
      <category>CS</category>
      <category>컴퓨터 구조</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/115</guid>
      <comments>https://codingisland.tistory.com/115#entry115comment</comments>
      <pubDate>Sun, 7 Sep 2025 15:23:07 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 이분 탐색 Binary Search</title>
      <link>https://codingisland.tistory.com/114</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이분 탐색&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;데이터들이 &lt;b&gt;&quot;정렬되있는 상태&quot;&lt;/b&gt;에서 &lt;u&gt;시작점과 끝점을 반씩 줄여나가며&lt;/u&gt; 원하는 데이터를 찾는 탐색 알고리즘이다.&lt;br /&gt;&lt;br /&gt;시간복잡도는 &lt;b&gt;O(logN)&lt;/b&gt;을 갖는다&lt;br /&gt;why? 반씩 덜어내며 탐색하기 때문이다.&lt;br /&gt;&lt;br /&gt;공간 복잡도는 &lt;b&gt;O(N)&lt;/b&gt;&lt;br /&gt;why? 별도의 메모리 공간을 더 확보하지 않기 때문이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드&lt;/blockquote&gt;
&lt;pre id=&quot;code_1751680827250&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

//비교 횟수 기록용
int gCount = 0;

//이분 탐색 검색
void BinarySearch(int tArray[], int Begin, int End, int data) {
    if (Begin &amp;gt; End) {
        cout &amp;lt;&amp;lt; gCount &amp;lt;&amp;lt; &quot;번의 비교만에 찾지 못함!&quot; &amp;lt;&amp;lt; endl;
        cout &amp;lt;&amp;lt; &quot;검색 실패&quot; &amp;lt;&amp;lt; endl;
        return;
    }

    int tMiddle = (Begin + End) / 2;
    gCount++;

    if (data == tArray[tMiddle]) {
        cout &amp;lt;&amp;lt; gCount &amp;lt;&amp;lt; &quot;번의 비교만에 찾음!&quot; &amp;lt;&amp;lt; endl;
        cout &amp;lt;&amp;lt; &quot;검색 성공&quot; &amp;lt;&amp;lt; endl;
    }
    else if (data &amp;lt; tArray[tMiddle]) {
        BinarySearch(tArray, Begin, tMiddle - 1, data);
    }
    else {
        BinarySearch(tArray, tMiddle + 1, End, data);
    }

}

int main() {
	int tArray[7] = { 1,2,8,9,11,19,29 };

	gCount = 0;
	BinarySearch(tArray, 0, 6, 29);

	gCount = 0;
	BinarySearch(tArray, 0, 6, 9);

	gCount = 0;
	BinarySearch(tArray, 0, 6, 777);

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;결과&lt;/blockquote&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;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkD9kG/btsO7etQXDA/4TvSEOhKS1KeVbNBfDSOCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkD9kG/btsO7etQXDA/4TvSEOhKS1KeVbNBfDSOCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkD9kG/btsO7etQXDA/4TvSEOhKS1KeVbNBfDSOCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkD9kG%2FbtsO7etQXDA%2F4TvSEOhKS1KeVbNBfDSOCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;402&quot; height=&quot;201&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>C++/알고리즘</category>
      <category>binarysearch</category>
      <category>c++</category>
      <category>알고리즘</category>
      <category>이분탐색</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/114</guid>
      <comments>https://codingisland.tistory.com/114#entry114comment</comments>
      <pubDate>Sat, 5 Jul 2025 11:01:28 +0900</pubDate>
    </item>
    <item>
      <title>[STL] 스택 Stack</title>
      <link>https://codingisland.tistory.com/113</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;스택&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;FILO 형태로 동작하며 가변형태를 가지는 자료구조&lt;/span&gt;이다.&lt;br /&gt;&lt;br /&gt;원시배열은 크기가 고정되어 있기 때문에, 가변배열이며 &lt;span style=&quot;color: #006dd7;&quot;&gt;스택의 출입구는 한 곳&lt;/span&gt;이므로 단방향 연결리스트로 구현하는 것이 좋다.&lt;br /&gt;&lt;br /&gt;스택은 메모리 저장에서 활용된다. 이런 형태를 구현하기 위해선 포인터의 이해가 필수이다.(나도 가끔 헷갈린다)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드&lt;/blockquote&gt;
&lt;pre id=&quot;code_1751680118462&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
스택Stack
FILO Frist In Last Out 형태로 동작하며, 가변형태를 가지는 자료구조이다.

원시배열은 크기가 고정되어 있기때문에,
가변 배열인 Single Linked List로 구현하는것이 적합하다.

Why?
스택은 출입구가 하나이므로 Single연결리스트로 push, pop시에 O(1)로 가능하기 때문이다.

*/

#include &amp;lt;iostream&amp;gt;
using namespace std;

struct SNode {
    //데이터
    int Data = 0;
    
    //다음 노드
    SNode* next = nullptr; //다음 노드(위에 아래로 내려가는 노드로 보면 된다.)
};

struct Stack {
    SNode* Head = nullptr; //스택은 가장 위에것만 사용

    // 삽입
    void Push(int x) {
        SNode* newNode = new SNode();
        newNode-&amp;gt;Data = x;
        newNode-&amp;gt;next = Head;
        Head = newNode;
    }

    //꺼내기
    void Pop() {
        //만약 스택의 헤드가 없으면 리턴. (스택이 비어있단 뜻)
        if (Head == nullptr) return;

        //여기왔다면 값이 있단 뜻
        //스택의 Head를 SNode의 next로 설정하고 기존 Head를 동적할당 해제한다.
        SNode* PopNode = Head;
        Head = PopNode-&amp;gt;next;

        delete PopNode;
    }

    void DisplayStack() {

        SNode* node = Head; //스택의 헤드를 가져온다.

        //nullptr이 아닐때까지 반복
        while (node != nullptr) {
            cout &amp;lt;&amp;lt; &quot;Stack값: &quot; &amp;lt;&amp;lt; node-&amp;gt;Data &amp;lt;&amp;lt; endl; //값 출력 후
            node = node-&amp;gt;next; //노드를 아래로 내린다.
        }
    }

    void Top() {
        cout &amp;lt;&amp;lt; &quot;Top값: &quot; &amp;lt;&amp;lt; Head-&amp;gt;Data &amp;lt;&amp;lt; endl;
    }

    bool isEmpty() {
        //헤드가 없으면 비어있단 뜻
        if (Head == nullptr) {
            return true;
        }
        return false;
    }

    //소멸자
    ~Stack() {
        while (!isEmpty()) {
            Pop(); //하나씩 꺼내며 해제
        }
    }

};


int main()
{
    Stack stack;
    stack.Push(3);
    stack.Push(5);
    stack.Push(6);
    stack.Push(2);
    stack.Push(4);

    stack.DisplayStack(); // 4 2 6 5 3
    cout &amp;lt;&amp;lt; &quot;-------------&quot; &amp;lt;&amp;lt; endl;

    stack.Pop(); //4꺼내기
    stack.Top(); //2값만 출력
    stack.Pop(); //2꺼내기

    stack.DisplayStack();// 6 5 3


    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;결과&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;110&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9IlsW/btsO5OwrlFR/vghhQRr1syfsPx79vzCnIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9IlsW/btsO5OwrlFR/vghhQRr1syfsPx79vzCnIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9IlsW/btsO5OwrlFR/vghhQRr1syfsPx79vzCnIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9IlsW%2FbtsO5OwrlFR%2FvghhQRr1syfsPx79vzCnIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;188&quot; height=&quot;320&quot; data-origin-width=&quot;110&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>C++/자료구조</category>
      <category>c++</category>
      <category>Stack</category>
      <category>stl</category>
      <category>스택</category>
      <author>rud_Island</author>
      <guid isPermaLink="true">https://codingisland.tistory.com/113</guid>
      <comments>https://codingisland.tistory.com/113#entry113comment</comments>
      <pubDate>Sat, 5 Jul 2025 10:50:04 +0900</pubDate>
    </item>
  </channel>
</rss>