<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>눈으로 개발하는 블로그</title>
    <link>https://carrotweb.tistory.com/</link>
    <description>Java를 기반으로 한 웹 개발에 필요한 환경과 기술들을 소개합니다.</description>
    <language>ko</language>
    <pubDate>Sun, 9 Aug 2026 08:08:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>carrotweb</managingEditor>
    <image>
      <title>눈으로 개발하는 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/2759149/attach/98ea264f0a594b4d99191374c2f73021</url>
      <link>https://carrotweb.tistory.com</link>
    </image>
    <item>
      <title>[19] Frontend 개발 - Capacitor, Geolocation, Google Map</title>
      <link>https://carrotweb.tistory.com/345</link>
      <description>&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;@capacitor/geolocation&amp;nbsp;설치 &lt;br /&gt;(&lt;a href=&quot;https://capacitorjs.com/docs/apis/geolocation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://capacitorjs.com/docs/apis/geolocation&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX5JRI/dJMcaiEkN2x/uhUkWPgwkskvqhkT9ZodQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX5JRI/dJMcaiEkN2x/uhUkWPgwkskvqhkT9ZodQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX5JRI/dJMcaiEkN2x/uhUkWPgwkskvqhkT9ZodQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX5JRI%2FdJMcaiEkN2x%2FuhUkWPgwkskvqhkT9ZodQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;b&gt;위치 기반 기능 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. geolocation 설치&amp;nbsp;(터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786088248160&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @capacitor/geolocation --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0MSe2/dJMcagGz1RI/GIDvbMB8lAVLgoswYBkXjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0MSe2/dJMcagGz1RI/GIDvbMB8lAVLgoswYBkXjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0MSe2/dJMcagGz1RI/GIDvbMB8lAVLgoswYBkXjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0MSe2%2FdJMcagGz1RI%2FGIDvbMB8lAVLgoswYBkXjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu에 LunchMenuMapView.vue 파일을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEafuA/dJMcacYoQdl/aK7cLb0XfGQqQnsk7IIdeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEafuA/dJMcacYoQdl/aK7cLb0XfGQqQnsk7IIdeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEafuA/dJMcacYoQdl/aK7cLb0XfGQqQnsk7IIdeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEafuA%2FdJMcacYoQdl%2FaK7cLb0XfGQqQnsk7IIdeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1786088327464&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;GeoLocation&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;나의 위치:&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;위도: {{ loc.lat }}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;경도: {{ loc.lng }}&amp;lt;/p&amp;gt;
        &amp;lt;button @click=&quot;getCurrentPosition&quot;&amp;gt;현 위치&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref } from 'vue';
import { Geolocation } from '@capacitor/geolocation';

export default {
    name : 'LunchMenuMapView',
    setup() {
        const loc = ref({
            lat: 0,
            lng: 0,
        });

    const getCurrentPosition = async () =&amp;gt; {
      const pos = await Geolocation.getCurrentPosition();
      loc.value = {
        lat: pos.coords.latitude,
        lng: pos.coords.longitude,
      };
    };

    return {
        getCurrentPosition,
        loc
    };
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\router\index.js&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1786088355238&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import LunchMenuMapView from '@/views/LunchMenu/LunchMenuMapView.vue'
:

const routes = [
  :
  {
    path: '/lunchmenumap',
    name: 'lunchmenumap',
    component: LunchMenuMapView
  },
  :
]

:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\App.vue&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1786088375001&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;header&quot;&amp;gt;
        &amp;lt;div class=&quot;topline&quot;&amp;gt;
            &amp;lt;div class=&quot;headmenu&quot;&amp;gt;
                &amp;lt;div v-if=&quot;isLogin()&quot;&amp;gt;
                    {{greetingMessage}}
                    &amp;lt;span @click=&quot;Logout()&quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div v-else&amp;gt;
                    &amp;lt;router-link :to=&quot;{ name: 'Login' }&quot;&amp;gt;로그인&amp;lt;/router-link&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;nav&amp;gt;
      &amp;lt;router-link to=&quot;/&quot;&amp;gt;Home&amp;lt;/router-link&amp;gt; |
      &amp;lt;router-link to=&quot;/about&quot;&amp;gt;About&amp;lt;/router-link&amp;gt; |
      &amp;lt;router-link to=&quot;/lunchmenu/list&quot;&amp;gt;LunchMenu&amp;lt;/router-link&amp;gt; |
      &amp;lt;router-link to=&quot;/lunchmenumap&quot;&amp;gt;LunchMenuMap&amp;lt;/router-link&amp;gt;
    &amp;lt;/nav&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;router-view/&amp;gt;
&amp;lt;/template&amp;gt;

:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenumap)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfdSaF/dJMcabL3XNL/A07bM5T2K4LCKKP8T6b2bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfdSaF/dJMcabL3XNL/A07bM5T2K4LCKKP8T6b2bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfdSaF/dJMcabL3XNL/A07bM5T2K4LCKKP8T6b2bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfdSaF%2FdJMcabL3XNL%2FA07bM5T2K4LCKKP8T6b2bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[현 위치] 버튼 클릭합니다. 권한 요청에서 [이번에만 허용 선택]을 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euYK8K/dJMcajpzmtX/Mh0pcoPYxqHknh56ZYYqZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euYK8K/dJMcajpzmtX/Mh0pcoPYxqHknh56ZYYqZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euYK8K/dJMcajpzmtX/Mh0pcoPYxqHknh56ZYYqZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeuYK8K%2FdJMcajpzmtX%2FMh0pcoPYxqHknh56ZYYqZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1362&quot; height=&quot;841&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;841&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;&lt;b&gt;Google&amp;nbsp;Map&amp;nbsp;연동&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue3-google-map&amp;nbsp;설치 &lt;br /&gt;(&lt;a href=&quot;https://vue3-google-map.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://vue3-google-map.com/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kNPTS/dJMcacYoQx1/sQS8yeGPnpg3as7SZyxmzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kNPTS/dJMcacYoQx1/sQS8yeGPnpg3as7SZyxmzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kNPTS/dJMcacYoQx1/sQS8yeGPnpg3as7SZyxmzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkNPTS%2FdJMcacYoQx1%2FsQS8yeGPnpg3as7SZyxmzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. vue3-google-map 설치&amp;nbsp;(터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786088597566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install vue3-google-map --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vGQZn/dJMcafHEQGR/LQLOC16Ua8y4ZTC4yeC6g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vGQZn/dJMcafHEQGR/LQLOC16Ua8y4ZTC4yeC6g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vGQZn/dJMcafHEQGR/LQLOC16Ua8y4ZTC4yeC6g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvGQZn%2FdJMcafHEQGR%2FLQLOC16Ua8y4ZTC4yeC6g1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuMapView.vue&amp;nbsp;파일에&amp;nbsp;GoogleMap를&amp;nbsp;추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1786088650060&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;GeoLocation&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;나의 위치:&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;위도: {{ loc.lat }}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;경도: {{ loc.lng }}&amp;lt;/p&amp;gt;
        &amp;lt;button @click=&quot;getCurrentPosition&quot;&amp;gt;현 위치&amp;lt;/button&amp;gt;
        &amp;lt;GoogleMap
          api-key=&quot;YOUR_GOOGLE_MAPS_API_KEY&quot;
          style=&quot;width: 100%; height: 400px&quot;
          :center=&quot;loc&quot;
          :zoom=&quot;15&quot;
          language=&quot;kor&quot;
          &amp;gt;
            &amp;lt;Marker :options=&quot;{ position: loc }&quot; /&amp;gt;
          &amp;lt;/GoogleMap&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref } from 'vue';
import { Geolocation } from '@capacitor/geolocation';
import { GoogleMap, Marker } from 'vue3-google-map';

export default {
  name : 'LunchMenuMapView',
  components: {
    GoogleMap,
    Marker
  },
  setup() {
    const loc = ref({
        lat: 0,
        lng: 0,
    });

    const getCurrentPosition = async () =&amp;gt; {
      const pos = await Geolocation.getCurrentPosition();
      loc.value = {
        lat: pos.coords.latitude,
        lng: pos.coords.longitude,
      };
    };

    return {
        getCurrentPosition,
        loc
    };
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenumap) &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWWNc4/dJMcafHEQMr/gYMkCiipiLEsBpCHWPxnL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWWNc4/dJMcafHEQMr/gYMkCiipiLEsBpCHWPxnL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWWNc4/dJMcafHEQMr/gYMkCiipiLEsBpCHWPxnL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWWNc4%2FdJMcafHEQMr%2FgYMkCiipiLEsBpCHWPxnL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[현 위치] 버튼 클릭을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CrMAq/dJMcaaM4VED/kTTmxewBFjgF58wzCGYnHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CrMAq/dJMcaaM4VED/kTTmxewBFjgF58wzCGYnHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CrMAq/dJMcaaM4VED/kTTmxewBFjgF58wzCGYnHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCrMAq%2FdJMcaaM4VED%2FkTTmxewBFjgF58wzCGYnHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;br /&gt;&lt;b&gt;Android&amp;nbsp;권한&amp;nbsp;설정&amp;nbsp;추가&lt;/b&gt;&lt;br /&gt;(&lt;a href=&quot;https://capacitorjs.com/docs/apis/geolocation&quot;&gt;https://capacitorjs.com/docs/apis/geolocation&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zTT3p/dJMcahZEXZP/lFUIo3rCkhEhZTQnJEjOA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zTT3p/dJMcahZEXZP/lFUIo3rCkhEhZTQnJEjOA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zTT3p/dJMcahZEXZP/lFUIo3rCkhEhZTQnJEjOA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzTT3p%2FdJMcahZEXZP%2FlFUIo3rCkhEhZTQnJEjOA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. lunchapp\android\app\src\main\AndroidManifest.xml 파일에 권한을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/phYNx/dJMcadJGDfR/iD8smHq5GrW6nAgEKN4EU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/phYNx/dJMcadJGDfR/iD8smHq5GrW6nAgEKN4EU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/phYNx/dJMcadJGDfR/iD8smHq5GrW6nAgEKN4EU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FphYNx%2FdJMcadJGDfR%2FiD8smHq5GrW6nAgEKN4EU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;956&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1786089249634&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&amp;gt;

    :

    &amp;lt;!-- Permissions --&amp;gt;

    &amp;lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&amp;gt;

    &amp;lt;!-- Geolocation Plugin --&amp;gt;
    &amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&amp;gt;
    &amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&amp;gt;
    &amp;lt;uses-feature android:name=&quot;android.hardware.location.gps&quot; /&amp;gt;
&amp;lt;/manifest&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;모바일 앱 배포&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Vue 빌드 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786088753606&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpEKFY/dJMcaf8zDE4/QbEXapVSX85C2SmxxWgRl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpEKFY/dJMcaf8zDE4/QbEXapVSX85C2SmxxWgRl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpEKFY/dJMcaf8zDE4/QbEXapVSX85C2SmxxWgRl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpEKFY%2FdJMcaf8zDE4%2FQbEXapVSX85C2SmxxWgRl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;Android&amp;nbsp;/&amp;nbsp;iOS&amp;nbsp;동기화&amp;nbsp;(터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786088850259&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx cap sync&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bumTKm/dJMcafgyZfo/KPE2Xs1acPBgs5IRNrcJR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bumTKm/dJMcafgyZfo/KPE2Xs1acPBgs5IRNrcJR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bumTKm/dJMcafgyZfo/KPE2Xs1acPBgs5IRNrcJR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbumTKm%2FdJMcafgyZfo%2FKPE2Xs1acPBgs5IRNrcJR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;3.&amp;nbsp;Android&amp;nbsp;프로젝트&amp;nbsp;오픈&lt;/p&gt;
&lt;pre id=&quot;code_1786088866376&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx cap open android&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWkrOz/dJMcadiLGxF/CvrPXZCtmzKaSWNK2eOPgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWkrOz/dJMcadiLGxF/CvrPXZCtmzKaSWNK2eOPgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWkrOz/dJMcadiLGxF/CvrPXZCtmzKaSWNK2eOPgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWkrOz%2FdJMcadiLGxF%2FCvrPXZCtmzKaSWNK2eOPgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;Android 스튜디오에서 직접 권한 설정을 추가하기 위해서 app\manifests\AndroidManifest.xml 파일에 권한을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dibNhj/dJMcaazyEKU/0HMPAbKbLiSA2roj2460u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dibNhj/dJMcaazyEKU/0HMPAbKbLiSA2roj2460u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dibNhj/dJMcaazyEKU/0HMPAbKbLiSA2roj2460u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdibNhj%2FdJMcaazyEKU%2F0HMPAbKbLiSA2roj2460u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 실행(Run)&amp;nbsp;버튼을&amp;nbsp;눌러&amp;nbsp;실행합니다.&amp;nbsp;(Emulator가&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WpmV8/dJMcajwufoI/J9AKrZ4IcFOmJ7JeEzYuP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WpmV8/dJMcajwufoI/J9AKrZ4IcFOmJ7JeEzYuP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WpmV8/dJMcajwufoI/J9AKrZ4IcFOmJ7JeEzYuP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWpmV8%2FdJMcajwufoI%2FJ9AKrZ4IcFOmJ7JeEzYuP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8rFm7/dJMcaiYvZQ5/IvBdeK4ZS3fhYkILEZFQK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8rFm7/dJMcaiYvZQ5/IvBdeK4ZS3fhYkILEZFQK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8rFm7/dJMcaiYvZQ5/IvBdeK4ZS3fhYkILEZFQK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8rFm7%2FdJMcaiYvZQ5%2FIvBdeK4ZS3fhYkILEZFQK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;위치]&amp;nbsp;버튼&amp;nbsp;클릭을&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zl4Pa/dJMcaiYvZRp/DnFLXQyRz0SNSDH2wfwcU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zl4Pa/dJMcaiYvZRp/DnFLXQyRz0SNSDH2wfwcU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zl4Pa/dJMcaiYvZRp/DnFLXQyRz0SNSDH2wfwcU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZl4Pa%2FdJMcaiYvZRp%2FDnFLXQyRz0SNSDH2wfwcU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wn474/dJMcaiYvZRQ/C3GMXHO3LmlTCbKm66XI3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wn474/dJMcaiYvZRQ/C3GMXHO3LmlTCbKm66XI3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wn474/dJMcaiYvZRQ/C3GMXHO3LmlTCbKm66XI3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWn474%2FdJMcaiYvZRQ%2FC3GMXHO3LmlTCbKm66XI3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/defGUb/dJMcad30LPd/CxDXHaF3nk5TsPk3RqphSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/defGUb/dJMcad30LPd/CxDXHaF3nk5TsPk3RqphSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/defGUb/dJMcad30LPd/CxDXHaF3nk5TsPk3RqphSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdefGUb%2FdJMcad30LPd%2FCxDXHaF3nk5TsPk3RqphSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>capacitor geolocation</category>
      <category>vue3-google-map</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/345</guid>
      <comments>https://carrotweb.tistory.com/345#entry345comment</comments>
      <pubDate>Fri, 7 Aug 2026 17:02:53 +0900</pubDate>
    </item>
    <item>
      <title>[18] Frontend 개발 - Capacitor, Vue + Android</title>
      <link>https://carrotweb.tistory.com/344</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Capacitor를 사용하여 쉽게 앱을 개발해 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Capacitor &lt;br /&gt;(&lt;a href=&quot;https://capacitorjs.com)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://capacitorjs.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMO1O/dJMcaf1POny/pnHdDV8kAlrkpkGE3Kgmh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMO1O/dJMcaf1POny/pnHdDV8kAlrkpkGE3Kgmh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMO1O/dJMcaf1POny/pnHdDV8kAlrkpkGE3Kgmh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMO1O%2FdJMcaf1POny%2FpnHdDV8kAlrkpkGE3Kgmh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;&lt;b&gt;Web&amp;nbsp;App에&amp;nbsp;Capacitor&amp;nbsp;추가&lt;/b&gt; &lt;br /&gt;(&lt;a href=&quot;https://capacitorjs.com/docs/getting-started#add-capacitor-to-your-web-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://capacitorjs.com/docs/getting-started#add-capacitor-to-your-web-app&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq71lB/dJMb99USWfp/nbLQCHm5zkITUigRuCC7X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq71lB/dJMb99USWfp/nbLQCHm5zkITUigRuCC7X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq71lB/dJMb99USWfp/nbLQCHm5zkITUigRuCC7X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq71lB%2FdJMb99USWfp%2FnbLQCHm5zkITUigRuCC7X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;size16&quot;&gt;1. capacitor 설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786087261850&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @capacitor/core @capacitor/cli --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn5YHV/dJMcahlbviO/M5c7ra8sVwDRa2to0pL7F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn5YHV/dJMcahlbviO/M5c7ra8sVwDRa2to0pL7F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn5YHV/dJMcahlbviO/M5c7ra8sVwDRa2to0pL7F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn5YHV%2FdJMcahlbviO%2FM5c7ra8sVwDRa2to0pL7F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Capacitor 초기화 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786087304060&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx cap init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AM1W9/dJMcadbPVYu/Rr2krswKG5Kqt7lKokKV4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AM1W9/dJMcadbPVYu/Rr2krswKG5Kqt7lKokKV4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AM1W9/dJMcadbPVYu/Rr2krswKG5Kqt7lKokKV4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAM1W9%2FdJMcadbPVYu%2FRr2krswKG5Kqt7lKokKV4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;App&amp;nbsp;Name&amp;nbsp;:&amp;nbsp;앱&amp;nbsp;이름&amp;nbsp;(변경하지&amp;nbsp;않으려면&amp;nbsp;그냥&amp;nbsp;엔터&amp;nbsp;키를&amp;nbsp;누릅니다.&amp;nbsp;변경하려면&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;1333&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uA8Yr/dJMcajiWpZ5/Mk9e3WO6y6hQIBkdlODiJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uA8Yr/dJMcajiWpZ5/Mk9e3WO6y6hQIBkdlODiJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uA8Yr/dJMcajiWpZ5/Mk9e3WO6y6hQIBkdlODiJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuA8Yr%2FdJMcajiWpZ5%2FMk9e3WO6y6hQIBkdlODiJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1333&quot; height=&quot;165&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;165&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;Package&amp;nbsp;ID&amp;nbsp;:&amp;nbsp;앱의&amp;nbsp;패키지&amp;nbsp;아이디&amp;nbsp;(변경하지&amp;nbsp;않으려면&amp;nbsp;그냥&amp;nbsp;엔터&amp;nbsp;키를&amp;nbsp;누릅니다.&amp;nbsp;변경하려면&amp;nbsp;입력합니다.) &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqdML/dJMcaazyCEJ/YKS9iZKRtvoXohp19rTOqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqdML/dJMcaazyCEJ/YKS9iZKRtvoXohp19rTOqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqdML/dJMcaazyCEJ/YKS9iZKRtvoXohp19rTOqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqdML%2FdJMcaazyCEJ%2FYKS9iZKRtvoXohp19rTOqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1333&quot; height=&quot;193&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;193&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;Ionic 계정 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정을&amp;nbsp;생성하지&amp;nbsp;않겠습니다.&amp;nbsp;n를&amp;nbsp;입력하고&amp;nbsp;엔터&amp;nbsp;키를&amp;nbsp;누릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Android&amp;nbsp;/&amp;nbsp;iOS&amp;nbsp;Platforms&amp;nbsp;설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786087501547&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @capacitor/android @capacitor/ios --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KSNOj/dJMcadbPWdn/egpKtgQhjs2ohsECF9MCaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KSNOj/dJMcadbPWdn/egpKtgQhjs2ohsECF9MCaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KSNOj/dJMcadbPWdn/egpKtgQhjs2ohsECF9MCaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKSNOj%2FdJMcadbPWdn%2FegpKtgQhjs2ohsECF9MCaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Android / iOS Platforms 추가 (터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786087563384&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx cap add android
npx cap add ios&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rxD9B/dJMcagfpDEw/A2mQexWmLXkvoH0G32GxWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rxD9B/dJMcagfpDEw/A2mQexWmLXkvoH0G32GxWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rxD9B/dJMcagfpDEw/A2mQexWmLXkvoH0G32GxWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrxD9B%2FdJMcagfpDEw%2FA2mQexWmLXkvoH0G32GxWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;Android / iOS가 구성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcK7bV/dJMcagfpDFp/lHtXlfe60BmGdtXDaNX4Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcK7bV/dJMcagfpDFp/lHtXlfe60BmGdtXDaNX4Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcK7bV/dJMcagfpDFp/lHtXlfe60BmGdtXDaNX4Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcK7bV%2FdJMcagfpDFp%2FlHtXlfe60BmGdtXDaNX4Ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;&lt;b&gt;Android&amp;nbsp;/&amp;nbsp;iOS&amp;nbsp;Platforms&amp;nbsp;추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Vue 빌드 (터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786087652644&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Divlm/dJMb99N3JH1/k136qDkRPPqlFKCkI2EXeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Divlm/dJMb99N3JH1/k136qDkRPPqlFKCkI2EXeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Divlm/dJMb99N3JH1/k136qDkRPPqlFKCkI2EXeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDivlm%2FdJMb99N3JH1%2Fk136qDkRPPqlFKCkI2EXeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Android / iOS 동기화 (터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1786087682156&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx cap sync&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8sk0X/dJMcahrWaSH/PoUekMgPvwB8QQO07ZFzhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8sk0X/dJMcahrWaSH/PoUekMgPvwB8QQO07ZFzhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8sk0X/dJMcahrWaSH/PoUekMgPvwB8QQO07ZFzhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8sk0X%2FdJMcahrWaSH%2FPoUekMgPvwB8QQO07ZFzhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;Android&amp;nbsp;프로젝트&amp;nbsp;오픈 &lt;br /&gt;(&lt;a href=&quot;https://capacitorjs.com/docs/android#opening-the-android-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://capacitorjs.com/docs/android#opening-the-android-project&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vp27z/dJMcahrWaUd/NzCRceaYWA8sMkMTWrKecK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vp27z/dJMcahrWaUd/NzCRceaYWA8sMkMTWrKecK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vp27z/dJMcahrWaUd/NzCRceaYWA8sMkMTWrKecK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvp27z%2FdJMcahrWaUd%2FNzCRceaYWA8sMkMTWrKecK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;3. Android&amp;nbsp;프로젝트&amp;nbsp;오픈&lt;/p&gt;
&lt;pre id=&quot;code_1786087783895&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx cap open android&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eoo877/dJMcabrETi7/5CUDNUGfDttB7crakZeMmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eoo877/dJMcabrETi7/5CUDNUGfDttB7crakZeMmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eoo877/dJMcabrETi7/5CUDNUGfDttB7crakZeMmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feoo877%2FdJMcabrETi7%2F5CUDNUGfDttB7crakZeMmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;880&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;880&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;Android Studio가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Trust Project] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd8kWf/dJMb99USWRT/qdxU0s8U0eK0GOXVnoswb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd8kWf/dJMb99USWRT/qdxU0s8U0eK0GOXVnoswb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd8kWf/dJMb99USWRT/qdxU0s8U0eK0GOXVnoswb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd8kWf%2FdJMb99USWRT%2FqdxU0s8U0eK0GOXVnoswb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;190&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;315&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwKA9f/dJMcabynjK3/GLLSkK3TDj3d8NLHoDIcj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwKA9f/dJMcabynjK3/GLLSkK3TDj3d8NLHoDIcj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwKA9f/dJMcabynjK3/GLLSkK3TDj3d8NLHoDIcj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwKA9f%2FdJMcabynjK3%2FGLLSkK3TDj3d8NLHoDIcj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qjYjD/dJMcaidkLCo/DIBCuDiKEZyHMhQn2mwPIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjYjD/dJMcaidkLCo/DIBCuDiKEZyHMhQn2mwPIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjYjD/dJMcaidkLCo/DIBCuDiKEZyHMhQn2mwPIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjYjD%2FdJMcaidkLCo%2FDIBCuDiKEZyHMhQn2mwPIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/txspU/dJMcai5iEHY/MuiPtEV4sBEyb4sA3TKUG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/txspU/dJMcai5iEHY/MuiPtEV4sBEyb4sA3TKUG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/txspU/dJMcai5iEHY/MuiPtEV4sBEyb4sA3TKUG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtxspU%2FdJMcai5iEHY%2FMuiPtEV4sBEyb4sA3TKUG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 실행(Run) 버튼을 눌러 실행합니다. (Emulator가 실행됩니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyCUiY/dJMcaidkLDW/7BrEwCG3lRSIjlLMzC0nL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyCUiY/dJMcaidkLDW/7BrEwCG3lRSIjlLMzC0nL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyCUiY/dJMcaidkLDW/7BrEwCG3lRSIjlLMzC0nL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyCUiY%2FdJMcaidkLDW%2F7BrEwCG3lRSIjlLMzC0nL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/90xAm/dJMcag0IKoc/b39wydbikKRez12DuGODQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/90xAm/dJMcag0IKoc/b39wydbikKRez12DuGODQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/90xAm/dJMcag0IKoc/b39wydbikKRez12DuGODQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F90xAm%2FdJMcag0IKoc%2Fb39wydbikKRez12DuGODQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;1477&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btN54i/dJMcacRyF24/w4U2ThBPHUL3aaOikhrwz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btN54i/dJMcacRyF24/w4U2ThBPHUL3aaOikhrwz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btN54i/dJMcacRyF24/w4U2ThBPHUL3aaOikhrwz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtN54i%2FdJMcacRyF24%2Fw4U2ThBPHUL3aaOikhrwz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;922&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;922&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;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>Capacitor</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/344</guid>
      <comments>https://carrotweb.tistory.com/344#entry344comment</comments>
      <pubDate>Fri, 7 Aug 2026 16:37:10 +0900</pubDate>
    </item>
    <item>
      <title>Android 설치 - Hello Android 실행</title>
      <link>https://carrotweb.tistory.com/343</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Android&amp;nbsp;Studio&amp;nbsp;다운로드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Android Developer (&lt;a href=&quot;https://developer.android.com/?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/?hl=ko&lt;/a&gt;) 사이트로 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAB7E1/dJMcahyEc0Z/Tb79LsU83ZytIWofswJgok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAB7E1/dJMcahyEc0Z/Tb79LsU83ZytIWofswJgok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAB7E1/dJMcahyEc0Z/Tb79LsU83ZytIWofswJgok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAB7E1%2FdJMcahyEc0Z%2FTb79LsU83ZytIWofswJgok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;955&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. [Android 스튜디오 다운로드]를 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFwasb/dJMcagzH4sX/32xv9GnvlwWaD1kOsYciK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFwasb/dJMcagzH4sX/32xv9GnvlwWaD1kOsYciK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFwasb/dJMcagzH4sX/32xv9GnvlwWaD1kOsYciK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFwasb%2FdJMcagzH4sX%2F32xv9GnvlwWaD1kOsYciK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;955&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&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;3. 약관를 아래로 스크롤한 후 동의를 체크하고 하고 [다운로드]를 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQncj0/dJMcaa7qoOq/MlgFZwox2sj8SwzGWJVWX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQncj0/dJMcaa7qoOq/MlgFZwox2sj8SwzGWJVWX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQncj0/dJMcaa7qoOq/MlgFZwox2sj8SwzGWJVWX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQncj0%2FdJMcaa7qoOq%2FMlgFZwox2sj8SwzGWJVWX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;955&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnBFWA/dJMcabL3UcI/dLey7o0kZUkA7HkocrHSK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnBFWA/dJMcabL3UcI/dLey7o0kZUkA7HkocrHSK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnBFWA/dJMcabL3UcI/dLey7o0kZUkA7HkocrHSK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnBFWA%2FdJMcabL3UcI%2FdLey7o0kZUkA7HkocrHSK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;955&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;955&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 Android Studio Quail 2 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Android Studio가 다운로드 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxYYDX/dJMcabd1siF/CctvbLLasGHhKjOExKvTgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxYYDX/dJMcabd1siF/CctvbLLasGHhKjOExKvTgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxYYDX/dJMcabd1siF/CctvbLLasGHhKjOExKvTgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxYYDX%2FdJMcabd1siF%2FCctvbLLasGHhKjOExKvTgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;956&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;956&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;&lt;b&gt;Android&amp;nbsp;Studio&amp;nbsp;설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzVgaV/dJMcacRyDlP/BqJK3WSHFZKDES3NMkd0bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzVgaV/dJMcacRyDlP/BqJK3WSHFZKDES3NMkd0bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzVgaV/dJMcacRyDlP/BqJK3WSHFZKDES3NMkd0bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzVgaV%2FdJMcacRyDlP%2FBqJK3WSHFZKDES3NMkd0bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;447&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 설치할 컴퍼넌트를 선택하고 [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs0Fmn/dJMcadJGzBu/3ztiqMLCjRkcAQzMzLI7e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs0Fmn/dJMcadJGzBu/3ztiqMLCjRkcAQzMzLI7e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs0Fmn/dJMcadJGzBu/3ztiqMLCjRkcAQzMzLI7e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs0Fmn%2FdJMcadJGzBu%2F3ztiqMLCjRkcAQzMzLI7e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;447&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&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;3. 설치 경로를 설정하고 [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;871&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dH7tni/dJMcagT3iVO/449M0qDMzJsKvuyKz7JAxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dH7tni/dJMcagT3iVO/449M0qDMzJsKvuyKz7JAxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dH7tni/dJMcagT3iVO/449M0qDMzJsKvuyKz7JAxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdH7tni%2FdJMcagT3iVO%2F449M0qDMzJsKvuyKz7JAxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1349&quot; height=&quot;871&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;871&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 설치 이름을 설정하고 [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1xDYX/dJMcacRyDtY/WIK23P4bFQTjBqRndts0pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1xDYX/dJMcacRyDtY/WIK23P4bFQTjBqRndts0pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1xDYX/dJMcacRyDtY/WIK23P4bFQTjBqRndts0pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1xDYX%2FdJMcacRyDtY%2FWIK23P4bFQTjBqRndts0pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;447&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&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;5. 설치가 진행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkFB7i/dJMcagl9Pic/FvsaF5JroZUPVXDxxypVbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkFB7i/dJMcagl9Pic/FvsaF5JroZUPVXDxxypVbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkFB7i/dJMcagl9Pic/FvsaF5JroZUPVXDxxypVbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkFB7i%2FdJMcagl9Pic%2FFvsaF5JroZUPVXDxxypVbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;886&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 설치가 완료되면 [Finish] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSSoC4/dJMcaaffGFV/3N19JRgKaATisevPTKxCNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSSoC4/dJMcaaffGFV/3N19JRgKaATisevPTKxCNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSSoC4/dJMcaaffGFV/3N19JRgKaATisevPTKxCNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSSoC4%2FdJMcaaffGFV%2F3N19JRgKaATisevPTKxCNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;447&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;581&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;&lt;b&gt;Android Studio 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 개선에 도움을 주실거면 [Send usage statistics to Google] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkfy0p/dJMcadv7mtP/S1hFZDBbnlvXYhTNJkEnGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkfy0p/dJMcadv7mtP/S1hFZDBbnlvXYhTNJkEnGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkfy0p/dJMcadv7mtP/S1hFZDBbnlvXYhTNJkEnGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkfy0p%2FdJMcadv7mtP%2FS1hFZDBbnlvXYhTNJkEnGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;357&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ8FSl/dJMcab6gyJO/FiY9a8cvBuBYRYn9jreTe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ8FSl/dJMcab6gyJO/FiY9a8cvBuBYRYn9jreTe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ8FSl/dJMcab6gyJO/FiY9a8cvBuBYRYn9jreTe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ8FSl%2FdJMcab6gyJO%2FFiY9a8cvBuBYRYn9jreTe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;649&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&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;3. 설치 타입을 [Standard]를 선택하고 [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcQUsz/dJMcaaTNV36/MrxeWbZv61W6KfAk4nFqVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcQUsz/dJMcaaTNV36/MrxeWbZv61W6KfAk4nFqVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcQUsz/dJMcaaTNV36/MrxeWbZv61W6KfAk4nFqVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcQUsz%2FdJMcaaTNV36%2FMrxeWbZv61W6KfAk4nFqVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 설치 정보가 나타납니다. [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/96hyn/dJMcaalTZRi/LKQo5j0WAxXmKBhttNBlv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/96hyn/dJMcaalTZRi/LKQo5j0WAxXmKBhttNBlv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/96hyn/dJMcaalTZRi/LKQo5j0WAxXmKBhttNBlv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F96hyn%2FdJMcaalTZRi%2FLKQo5j0WAxXmKBhttNBlv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&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;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egqZna/dJMcaalTZRO/wHbfUUT1Lm1uRhWjr2UbrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egqZna/dJMcaalTZRO/wHbfUUT1Lm1uRhWjr2UbrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egqZna/dJMcaalTZRO/wHbfUUT1Lm1uRhWjr2UbrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegqZna%2FdJMcaalTZRO%2FwHbfUUT1Lm1uRhWjr2UbrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&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;5. 라이센스에 동의(Accept)를 선택하고 [Next] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRKb3i/dJMcahFmiFM/VU6Kg8O7xUfJ5KbCLgyVr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRKb3i/dJMcahFmiFM/VU6Kg8O7xUfJ5KbCLgyVr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRKb3i/dJMcahFmiFM/VU6Kg8O7xUfJ5KbCLgyVr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRKb3i%2FdJMcahFmiFM%2FVU6Kg8O7xUfJ5KbCLgyVr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&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;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uB3wM/dJMcaiK02Ak/Q8IeWckwvWG1pOGjsRMjf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uB3wM/dJMcaiK02Ak/Q8IeWckwvWG1pOGjsRMjf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uB3wM/dJMcaiK02Ak/Q8IeWckwvWG1pOGjsRMjf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuB3wM%2FdJMcaiK02Ak%2FQ8IeWckwvWG1pOGjsRMjf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 설치가 진행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eeOniB/dJMcac489Wk/dwy5Adq1MrnMJKkMSRyes0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eeOniB/dJMcac489Wk/dwy5Adq1MrnMJKkMSRyes0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eeOniB/dJMcac489Wk/dwy5Adq1MrnMJKkMSRyes0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeeOniB%2FdJMcac489Wk%2Fdwy5Adq1MrnMJKkMSRyes0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&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. 설치가 완료되면 [Finish] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QjuyC/dJMcaaTNV8X/4QUmZcemkzIxVXeEQJImZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QjuyC/dJMcaaTNV8X/4QUmZcemkzIxVXeEQJImZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QjuyC/dJMcaaTNV8X/4QUmZcemkzIxVXeEQJImZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQjuyC%2FdJMcaaTNV8X%2F4QUmZcemkzIxVXeEQJImZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;930&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;930&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;&lt;b&gt;Android Studio 프로젝트 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. [New Project]를 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EsfLM/dJMcaaTNV9d/4lo3zaXPXkWQfWrPDKtpw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EsfLM/dJMcaaTNV9d/4lo3zaXPXkWQfWrPDKtpw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EsfLM/dJMcaaTNV9d/4lo3zaXPXkWQfWrPDKtpw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEsfLM%2FdJMcaaTNV9d%2F4lo3zaXPXkWQfWrPDKtpw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;980&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 실행될 환경과 프로젝트 UI 구성을 선택합니다. [Empty Activity]를 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQCbVf/dJMcahenUft/c4SqCaqBO1Zx38Rsk1Ng4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQCbVf/dJMcahenUft/c4SqCaqBO1Zx38Rsk1Ng4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQCbVf/dJMcahenUft/c4SqCaqBO1Zx38Rsk1Ng4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQCbVf%2FdJMcahenUft%2Fc4SqCaqBO1Zx38Rsk1Ng4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;897&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;897&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;3. 프로젝트 이름과 패키지 이름, 저장 경로, SDK를 선택하고 [Finish] 버튼을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL13ts/dJMcaa0zjiC/FEGftlBdyqU4fACY5uAsCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL13ts/dJMcaa0zjiC/FEGftlBdyqU4fACY5uAsCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL13ts/dJMcaa0zjiC/FEGftlBdyqU4fACY5uAsCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL13ts%2FdJMcaa0zjiC%2FFEGftlBdyqU4fACY5uAsCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;897&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;897&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;1241&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAQa4F/dJMcaa0zjiS/XfgP6th0442vshB0T7uAIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAQa4F/dJMcaa0zjiS/XfgP6th0442vshB0T7uAIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAQa4F/dJMcaa0zjiS/XfgP6th0442vshB0T7uAIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAQa4F%2FdJMcaa0zjiS%2FXfgP6th0442vshB0T7uAIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;897&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;897&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 기본 소스 코드를 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oV3Ky/dJMcaa0zjiZ/BUDnys1VMOpKKoz4b3G9kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oV3Ky/dJMcaa0zjiZ/BUDnys1VMOpKKoz4b3G9kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oV3Ky/dJMcaa0zjiZ/BUDnys1VMOpKKoz4b3G9kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoV3Ky%2FdJMcaa0zjiZ%2FBUDnys1VMOpKKoz4b3G9kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&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;UI 없이 인사말인 &quot;Hello Android&quot;가 출력되는 코드입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLbuYz/dJMcaf1PM3O/SD8HpV2lYDeGaFEHaTMLoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLbuYz/dJMcaf1PM3O/SD8HpV2lYDeGaFEHaTMLoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLbuYz/dJMcaf1PM3O/SD8HpV2lYDeGaFEHaTMLoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLbuYz%2FdJMcaf1PM3O%2FSD8HpV2lYDeGaFEHaTMLoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&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;5. 처음에는 실행(Run) 버튼이 비활성화 되어 있습니다. 실행(Run) 버튼 왼쪽에 있는 [Add Configuration]를 클릭하여 가상 환경을 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oKSJS/dJMcaf1PM43/pyIGpo5D3jChN4EE3slQm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oKSJS/dJMcaf1PM43/pyIGpo5D3jChN4EE3slQm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oKSJS/dJMcaf1PM43/pyIGpo5D3jChN4EE3slQm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoKSJS%2FdJMcaf1PM43%2FpyIGpo5D3jChN4EE3slQm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 실행(Run) 버튼을 누르면 Emulator가 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHpyIC/dJMcahyEd86/PYmIKf9TzLjYpgqKW1U6r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHpyIC/dJMcahyEd86/PYmIKf9TzLjYpgqKW1U6r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHpyIC/dJMcahyEd86/PYmIKf9TzLjYpgqKW1U6r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHpyIC%2FdJMcahyEd86%2FPYmIKf9TzLjYpgqKW1U6r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&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;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmhjYI/dJMcaaffGZS/ZFY8bFsFhMqLzOiKkQHuOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmhjYI/dJMcaaffGZS/ZFY8bFsFhMqLzOiKkQHuOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmhjYI/dJMcaaffGZS/ZFY8bFsFhMqLzOiKkQHuOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmhjYI%2FdJMcaaffGZS%2FZFY8bFsFhMqLzOiKkQHuOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&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;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctk0IR/dJMcaaffGZ9/d3m5qKl4SYlenusCdGm2PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctk0IR/dJMcaaffGZ9/d3m5qKl4SYlenusCdGm2PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctk0IR/dJMcaaffGZ9/d3m5qKl4SYlenusCdGm2PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fctk0IR%2FdJMcaaffGZ9%2Fd3m5qKl4SYlenusCdGm2PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;7. &lt;/span&gt;Emulator가 실행된 후 앱이 앱이 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oMnag/dJMcacjKsFJ/d42QFSnFK5cI12LjZBqChK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oMnag/dJMcacjKsFJ/d42QFSnFK5cI12LjZBqChK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oMnag/dJMcacjKsFJ/d42QFSnFK5cI12LjZBqChK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoMnag%2FdJMcacjKsFJ%2Fd42QFSnFK5cI12LjZBqChK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&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;Hello Android&quot;가 출력됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC81Hk/dJMb991H9gG/kvVwKKuwZXKjn3IaJy2AP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC81Hk/dJMb991H9gG/kvVwKKuwZXKjn3IaJy2AP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC81Hk/dJMb991H9gG/kvVwKKuwZXKjn3IaJy2AP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC81Hk%2FdJMb991H9gG%2FkvVwKKuwZXKjn3IaJy2AP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&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;8. 중지(Stop) 버튼을 눌러 앱 실행을 종료합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chByg3/dJMcaij1nFv/0q7e1KxjpKMvJ8lYEJESWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chByg3/dJMcaij1nFv/0q7e1KxjpKMvJ8lYEJESWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chByg3/dJMcaij1nFv/0q7e1KxjpKMvJ8lYEJESWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchByg3%2FdJMcaij1nFv%2F0q7e1KxjpKMvJ8lYEJESWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;921&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>android studio</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/343</guid>
      <comments>https://carrotweb.tistory.com/343#entry343comment</comments>
      <pubDate>Fri, 7 Aug 2026 16:20:33 +0900</pubDate>
    </item>
    <item>
      <title>[17] Frontend 개발 - Store State 유지, Cookies, localStorage, Pinia Plugin Persistedstate</title>
      <link>https://carrotweb.tistory.com/342</link>
      <description>&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;웹 브라우저에서 새로고침을 하면 Vue가 초기화 되면서 Store가 초기화 됩니다.&lt;/p&gt;
&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;Store에 있는 State를 유지하기 위해서는 state의 정보를 저장하고 초기화되면 다시 읽어 들어 유지하게 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State의 정보를 저장하고 유지하기 위해 다음과 같이 세 가지 방법이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cookie(쿠키)를 사용하는 방법&lt;/li&gt;
&lt;li&gt;Storage(스토리지)를 사용하는 방법&lt;/li&gt;
&lt;li&gt;Pinia&amp;nbsp;Plugin&amp;nbsp;Persistedstate를&amp;nbsp;사용하는&amp;nbsp;방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Store State 유지 - &lt;/b&gt;&lt;b&gt;Cookie(쿠키)&amp;nbsp;사용&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저에서 F12키를 누르면 개발자 툴이 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Application를 클릭하고 Cookies를 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdlp9O/dJMb99URpUo/KEUTJPYAxGDryZmfezBbE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdlp9O/dJMb99URpUo/KEUTJPYAxGDryZmfezBbE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdlp9O/dJMb99URpUo/KEUTJPYAxGDryZmfezBbE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdlp9O%2FdJMb99URpUo%2FKEUTJPYAxGDryZmfezBbE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;Cookie 구성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Name : 쿠키 이름&lt;/li&gt;
&lt;li&gt;Value : 쿠키 값&lt;/li&gt;
&lt;li&gt;Domain : 도메인 (쿠키를 수신하도록 허용된 호스트)&lt;/li&gt;
&lt;li&gt;Path : 경로 (쿠키 헤더를 보내기 위해 요청된 URL)&lt;/li&gt;
&lt;li&gt;Expires / Max-Age : 쿠키 만료 기간 / 쿠키 최대 사용기간&lt;/li&gt;
&lt;li&gt;Size : 쿠키 크기(byte)&lt;/li&gt;
&lt;li&gt;HTTP : HTTP 사용 여부 - true / false (true 이면 HTTP를 통해서만 쿠키를 서버로 보낼 수 있음)&lt;/li&gt;
&lt;li&gt;Secure : HTTPS 사용 여부 - true / false (true 이면 HTTPS를 통해서만 쿠키를 서버로 보낼 수 있음)&lt;/li&gt;
&lt;li&gt;SameSite : 사이트 제한 설정 - none, strict(기본값), lax로 설정 (쿠키를&amp;nbsp;자사(&lt;a href=&quot;http://www.home.com)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.home.com)&lt;/a&gt; 또는 동일 사이트(dev.home.com) 컨텍스트로 제한해야 하는지를 선언할 수 있습니다. CSRF(Cross-Site Request Forgery - 교차 사이트 요청 위조) 공격을 방지하기 위해 HTTP 쿠키에서 설정할 수 있는 속성입니다.)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;none : 모든 도메인에 쿠키가 전송&lt;/li&gt;
&lt;li&gt;strict : 동일한 사이트 내의 요청에만 전송&lt;/li&gt;
&lt;li&gt;lax : 동일한 사이트 내의 요청 또는 &quot;안전한&quot; HTTP 메서드를 사용하는 최상위 경로(/)인 경우 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SameParty : 동일 사이트 사용 여부 - true / false (true 이면 동일한 사이트 내의 요청에서 최상위 경로(/)인 경우 쿠키를 설정하거나 전송할 수 있음)&lt;/li&gt;
&lt;li&gt;Priority : 우선순위 - low, medium(기본값), high로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cookie&amp;nbsp;함수&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies에&amp;nbsp;저장하기 &lt;/p&gt;
&lt;pre id=&quot;code_1785923876573&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cookies.set(keyName: string, value: string, expireTimes?: string | number | Date, path?: string, domain?: string, secure?: boolean, sameSite?: string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies의 set() 메서드는 Cookies에 keyName으로 value를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies의 set() 메서드에서 만료 기간을 지정하지 않으면 현재 시간에서 하루(1d)가 추가되어 설정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&amp;nbsp;경로(Path)도&amp;nbsp;설정하지&amp;nbsp;않았기&amp;nbsp;때문에&amp;nbsp;최상위&amp;nbsp;경로(/)로&amp;nbsp;설정됩니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies에서&amp;nbsp;가져오기&lt;/p&gt;
&lt;pre id=&quot;code_1785923914778&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cookies.get(keyName: string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies의&amp;nbsp;get()&amp;nbsp;메서드는&amp;nbsp;Cookies에서&amp;nbsp;keyName으로&amp;nbsp;검색하여&amp;nbsp;저장된&amp;nbsp;쿠키를&amp;nbsp;가져옵니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies에서&amp;nbsp;제거하기 &lt;/p&gt;
&lt;pre id=&quot;code_1785923938110&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cookies.remove(keyName: string, path?: string, domain?: string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies의&amp;nbsp;remove()&amp;nbsp;메서드는&amp;nbsp;Cookies에서&amp;nbsp;keyName으로&amp;nbsp;검색하여&amp;nbsp;저장된&amp;nbsp;쿠키를&amp;nbsp;제거합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. vue3-cookies 설치하기 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785923561725&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install vue3-cookies --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yKiIa/dJMcacD9Ll0/x70VVPHLQrpSDybYzjDSN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yKiIa/dJMcacD9Ll0/x70VVPHLQrpSDybYzjDSN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yKiIa/dJMcacD9Ll0/x70VVPHLQrpSDybYzjDSN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyKiIa%2FdJMcacD9Ll0%2Fx70VVPHLQrpSDybYzjDSN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;863&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\stores\useLoginStore.js 파일을 수정합니다. - Storage에 State를 저장시키는 함수를 추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924024864&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import { useCookies } from &quot;vue3-cookies&quot;;


export const useLoginStore = defineStore('login', () =&amp;gt; {
    :
   const { cookies } = useCookies();
    :

    // Storage에서 state를 저장합니다.
    function saveStateToStorage() {
        const state = {
            access_token : access_token.value,
            refresh_token :  refresh_token.value,
            member_id : member_id.value,
            member_name : member_name.value
        }
        cookies.set(&quot;lunchapp.login&quot;, JSON.stringify(state));
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\stores\useLoginStore.js 파일을 수정합니다. - 로그인 함수에 추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924075647&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:

export const useLoginStore = defineStore('login', () =&amp;gt; {
    :

    // 로그인합니다.
    async function doLogin(memberInfo) {
        :

        try {
            const res = await axios.post(&quot;http://127.0.0.1:9000/api/v1/member/login&quot;, memberInfo);
            if (res.data.code == 200) {
                :
                // Storage에서 state를 저장합니다.
                saveStateToStorage();
                result = true;
            }
        }
        :
    }

    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 웹 브라우저에서 확인하기 (http://localhost:8080)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2zdGX/dJMcajb3dXl/nV359GTbdwxBrg2Wujy9o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2zdGX/dJMcajb3dXl/nV359GTbdwxBrg2Wujy9o1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2zdGX/dJMcajb3dXl/nV359GTbdwxBrg2Wujy9o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2zdGX%2FdJMcajb3dXl%2FnV359GTbdwxBrg2Wujy9o1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인을&amp;nbsp;하면&amp;nbsp;Application의&amp;nbsp;Cookies에&amp;nbsp;lunchapp.login이&amp;nbsp;저장되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application의&amp;nbsp;Cookies에&amp;nbsp;저장된&amp;nbsp;lunchapp.login의&amp;nbsp;내용은&amp;nbsp;다음과&amp;nbsp;같습니다. &lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Name : &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;lunchapp.login&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value : %7B%22access_token%22%3A%22eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwMTU2NzgsImV4cCI6MTc0NDAxNTczOCwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiI5Nzk0NzBkNWE0NmM0MGMyOWM3YWE0NzVhNTg5MDFhMSJ9.v1sxBNVVlKT0rUQzbbmFnvT-lwZApyICxfgMIh_rEjM%22%2C%22refresh_token%22%3A%22eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJSZWZyZXNoIiwic2NvcGUiOiJwcm9maWxlIHNjb3BlcyIsImlhdCI6MTc0NDAxNTY3OCwiZXhwIjoxNzQ0MDE1Nzk4LCJhdWQiOiJsdW5jaG1lbnVBcHAiLCJpc3MiOiJhcGlzZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0MSIsImp0aSI6ImI2ODkxNjEyYzNmOTRlNDNiMzE4YTNjYmZhYmNjZTU1In0.m99wsch3Uz2_Q0BpsPEmLpsN6Qo10XV0IU8BAj25QoA%22%2C%22member_id%22%3A%22test1%22%2C%22member_name%22%3A%22%ED%85%8C%EC%8A%A4%ED%8A%B81%22%7D&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Domain : localhost&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Path : /&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Expires / Max-Age : 2025-04-08T08:47:58.398Z&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Size : 876&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HTTP :&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Secure :&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SameSite&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; : Lax&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SameParty&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; :&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Priority : Medium&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 웹&amp;nbsp;브라우저를&amp;nbsp;새로고침(F5)하면&amp;nbsp;Cookie에는&amp;nbsp;값은&amp;nbsp;있지만&amp;nbsp;로그인이&amp;nbsp;유지되지&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q4a3z/dJMcaaTMrIb/KsMo8iBwIYohiGdTskXpF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q4a3z/dJMcaaTMrIb/KsMo8iBwIYohiGdTskXpF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q4a3z/dJMcaaTMrIb/KsMo8iBwIYohiGdTskXpF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq4a3z%2FdJMcaaTMrIb%2FKsMo8iBwIYohiGdTskXpF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. src\stores\useLoginStore.js 파일을 수정합니다. - Storage에서 State를 읽어오는 함수를 추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924276329&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useLoginStore = defineStore('login', () =&amp;gt; {
    :

    // Storage에서 state를 읽어옵니다.
    function readStateFromStorage() {
        if (cookies.get(&quot;testvue.login&quot;) != null) {
            const state = cookies.get(&quot;testvue.login&quot;);
            if (state.access_token != null) {
                access_token.value = state.access_token;
            }
            if (state.refresh_token != null) {
                refresh_token.value = state.refresh_token;
            }
            if (state.member_id != null) {
                member_id.value = state.member_id;
            }
            if (state.member_name != null) {
                member_name.value = state.member_name;
            }
        }
    }

    return {
        :
        doLogout,
        readStateFromStorage
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cookies의&amp;nbsp;get()&amp;nbsp;메서드로&amp;nbsp;JSON&amp;nbsp;객체를&amp;nbsp;가져올&amp;nbsp;때는&amp;nbsp;자동으로&amp;nbsp;JSON&amp;nbsp;객체로&amp;nbsp;변환됩니다.&amp;nbsp;JSON.parse()&amp;nbsp;메서드를&amp;nbsp;사용하지&amp;nbsp;않아도&amp;nbsp;됩니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. src\App.vue 파일을 수정합니다. - onMounted()를 추가하여 Storage에 있는 State를 읽어오게 합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924314789&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { computed, onMounted } from 'vue';
:

export default {
    name: 'AppView',
    setup() {
        :

        onMounted(()=&amp;gt; {
            loginStore.readStateFromStorage();
        });

        :
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 웹&amp;nbsp;브라우저를&amp;nbsp;새로고침(F5)하면&amp;nbsp;Cookie에&amp;nbsp;있는&amp;nbsp;값을&amp;nbsp;로그인을&amp;nbsp;유지하는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0LYV8/dJMcahZDpC9/zl0kzBHKy3HsBl5NEMSsI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0LYV8/dJMcahZDpC9/zl0kzBHKy3HsBl5NEMSsI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0LYV8/dJMcahZDpC9/zl0kzBHKy3HsBl5NEMSsI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0LYV8%2FdJMcahZDpC9%2Fzl0kzBHKy3HsBl5NEMSsI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;9. src\stores\useLoginStore.js 파일을 수정합니다. - 로그아웃이 되면 쿠키가 삭제되게 합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924394215&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useLoginStore = defineStore('login', () =&amp;gt; {
    :

    // 로그아웃합니다.
    async function doLogout() {
        console.log(&quot;로그아웃되었습니다.&quot;);
        access_token.value = &quot;&quot;;
        refresh_token.value = &quot;&quot;;
        member_id.value = &quot;&quot;;
        member_name.value = &quot;&quot;;
        cookies.remove(&quot;lunchapp.login&quot;);
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 로그아웃을&amp;nbsp;하면&amp;nbsp;Cookie가&amp;nbsp;삭제되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Vt3M/dJMcahrUAXB/cuY6Ck0tfolLRzOQ8gVtk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Vt3M/dJMcahrUAXB/cuY6Ck0tfolLRzOQ8gVtk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Vt3M/dJMcahrUAXB/cuY6Ck0tfolLRzOQ8gVtk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Vt3M%2FdJMcahrUAXB%2FcuY6Ck0tfolLRzOQ8gVtk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;&lt;b&gt;Store State 유지 - Storage(스토리지)&amp;nbsp;사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 개발자&amp;nbsp;툴에서&amp;nbsp;Application를&amp;nbsp;클릭하고&amp;nbsp;Local&amp;nbsp;storage를&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NT7yU/dJMcacYnkdS/u6YaOVXGP8pQ4hrt1Sn5LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NT7yU/dJMcacYnkdS/u6YaOVXGP8pQ4hrt1Sn5LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NT7yU/dJMcacYnkdS/u6YaOVXGP8pQ4hrt1Sn5LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNT7yU%2FdJMcacYnkdS%2Fu6YaOVXGP8pQ4hrt1Sn5LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;b&gt;Storage 구성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Storage는 키(key)와 값(value)으로 구성되어 저장되고 키(key)로 조회됩니다.&lt;/li&gt;
&lt;li&gt;Storage는 Local Storage(로컬 스토리지)와 Session Storage(섹션 스토리지)가 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Local Storage(로컬 스토리지)는 영구적으로 저장됩니다. (삭제하지 않는 한 계속 유지됩니다.)&lt;/li&gt;
&lt;li&gt;Session&amp;nbsp;Storage(섹션&amp;nbsp;스토리지)는&amp;nbsp;임시적으로&amp;nbsp;저장됩니다.&amp;nbsp;(웹&amp;nbsp;브라우저가&amp;nbsp;종료되면&amp;nbsp;사라집니다.)&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Storage&amp;nbsp;함수&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local&amp;nbsp;storage에&amp;nbsp;저장하기&lt;/p&gt;
&lt;pre id=&quot;code_1785924566643&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;localStorage.setItem(key: string, value: string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localStorage의&amp;nbsp;setItem()&amp;nbsp;메서드는&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;key로&amp;nbsp;value를&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;Local&amp;nbsp;storage에서&amp;nbsp;가져오기 &lt;/p&gt;
&lt;pre id=&quot;code_1785924588520&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;localStorage.getItem(key: string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localStorage의&amp;nbsp;getItem()&amp;nbsp;메서드는&amp;nbsp;Local&amp;nbsp;storage에서&amp;nbsp;key로&amp;nbsp;검색하여&amp;nbsp;저장된&amp;nbsp;값을&amp;nbsp;가져옵니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local&amp;nbsp;storage에서&amp;nbsp;제거하기 &lt;/p&gt;
&lt;pre id=&quot;code_1785924607870&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;localStorage.removeItem(key: string)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localStorage의&amp;nbsp;removeItem()&amp;nbsp;메서드는&amp;nbsp;Local&amp;nbsp;storage에서&amp;nbsp;key로&amp;nbsp;검색하여&amp;nbsp;저장된&amp;nbsp;값을&amp;nbsp;제거합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores\useLoginStore.js 파일을 수정합니다. - Cookies를 제거하고 localStorage로 변경합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924650770&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
//import { useCookies } from &quot;vue3-cookies&quot;;


export const useLoginStore = defineStore('login', () =&amp;gt; {
    :
   //const { cookies } = useCookies();
    :

    // Storage에서 state를 저장합니다.
    function saveStateToStorage() {
        const state = {
            access_token : access_token.value,
            refresh_token :  refresh_token.value,
            member_id : member_id.value,
            member_name : member_name.value
        }
        //cookies.set(&quot;lunchapp.login&quot;, JSON.stringify(state));
        localStorage.setItem(&quot;lunchapp.login&quot;, JSON.stringify(state));
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\stores\useLoginStore.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&amp;nbsp;-&amp;nbsp;Cookies를&amp;nbsp;제거하고&amp;nbsp;localStorage로&amp;nbsp;변경합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785924687024&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useLoginStore = defineStore('login', () =&amp;gt; {
    :

    // Storage에서 state를 읽어옵니다.
    function readStateFromStorage() {
        //if (cookies.get(&quot;lunchapp.login&quot;) != null) {
        //    const state = cookies.get(&quot;lunchapp.login&quot;);
        if (localStorage.getItem(&quot;lunchapp.login&quot;) != null) {
            const state =  JSON.parse(localStorage.getItem(&quot;lunchapp.login&quot;));
            if (state.access_token != null) {
                access_token.value = state.access_token;
            }
            if (state.refresh_token != null) {
                refresh_token.value = state.refresh_token;
            }
            if (state.member_id != null) {
                member_id.value = state.member_id;
            }
            if (state.member_name != null) {
                member_name.value = state.member_name;
            }
        }
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON.parse()&amp;nbsp;메서드를&amp;nbsp;사용하여&amp;nbsp;JSON&amp;nbsp;객체로&amp;nbsp;변환해야&amp;nbsp;합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\stores\useLoginStore.js 파일을 수정합니다. - Cookies를 제거하고 localStorage로 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785924727662&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useLoginStore = defineStore('login', () =&amp;gt; {
    :

    // 로그아웃합니다.
    async function doLogout() {
        console.log(&quot;로그아웃되었습니다.&quot;);
        access_token.value = &quot;&quot;;
        refresh_token.value = &quot;&quot;;
        member_id.value = &quot;&quot;;
        member_name.value = &quot;&quot;;
        //cookies.remove(&quot;lunchapp.login&quot;);
        localStorage.removeItem(&quot;lunchapp.login&quot;);
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 로그인을&amp;nbsp;하면&amp;nbsp;Application의&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;lunchapp.login이&amp;nbsp;저장되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drfBUe/dJMcac47DPU/7wMzJbrFkaPYCcTiSSS6T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drfBUe/dJMcac47DPU/7wMzJbrFkaPYCcTiSSS6T1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drfBUe/dJMcac47DPU/7wMzJbrFkaPYCcTiSSS6T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrfBUe%2FdJMcac47DPU%2F7wMzJbrFkaPYCcTiSSS6T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;Application의&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;저장된&amp;nbsp;lunchapp.login의&amp;nbsp;내용은&amp;nbsp;다음과&amp;nbsp;같습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key&amp;nbsp;:&amp;nbsp;lunchapp.login &lt;br /&gt;Value&amp;nbsp;:&amp;nbsp;{&quot;access_token&quot;:&quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwMzUyMzAsImV4cCI6MTc0NDAzNTI5MCwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiIwMWMzNWE1YWVlMmE0YjA1YjM0ZGQ4OWI1MmEwZDZhNiJ9.Smocyex5QHHNigkYh6OTbhqRUK2EtLzKT1oO8Mat3tU&quot;,&quot;refresh_token&quot;:&quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJSZWZyZXNoIiwic2NvcGUiOiJwcm9maWxlIHNjb3BlcyIsImlhdCI6MTc0NDAzNTIzMCwiZXhwIjoxNzQ0MDM1MzUwLCJhdWQiOiJsdW5jaG1lbnVBcHAiLCJpc3MiOiJhcGlzZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0MSIsImp0aSI6IjdhYWYyMzU0MTIzZDQxNGE5ZTU1YzdjZmQ4MmQyMzQzIn0.P8pc8rT52XZZCzGrB-WUC6pVRt5hMcZ_qD2A6XKkEB0&quot;,&quot;member_id&quot;:&quot;test1&quot;,&quot;member_name&quot;:&quot;테스트1&quot;} &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 로그아웃을&amp;nbsp;하면&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;있는&amp;nbsp;lunchapp.login이&amp;nbsp;삭제되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db6KVF/dJMcaixyRB7/ZrDU5V9cDc20L4NDzyrZkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db6KVF/dJMcaixyRB7/ZrDU5V9cDc20L4NDzyrZkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db6KVF/dJMcaixyRB7/ZrDU5V9cDc20L4NDzyrZkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb6KVF%2FdJMcaixyRB7%2FZrDU5V9cDc20L4NDzyrZkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;&lt;b&gt;Pinia&amp;nbsp;Plugin&amp;nbsp;Persistedstate&amp;nbsp;사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia&amp;nbsp;Plugin&amp;nbsp;Persistedstate&amp;nbsp;(피니아&amp;nbsp;플러그인&amp;nbsp;퍼시스트드스테이트) &lt;br /&gt;(&lt;a href=&quot;https://prazdevs.github.io/pinia-plugin-persistedstate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://prazdevs.github.io/pinia-plugin-persistedstate/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Min6E/dJMcadiJ32I/7yrK1Xv9we2xTID2U9j5y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Min6E/dJMcadiJ32I/7yrK1Xv9we2xTID2U9j5y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Min6E/dJMcadiJ32I/7yrK1Xv9we2xTID2U9j5y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMin6E%2FdJMcadiJ32I%2F7yrK1Xv9we2xTID2U9j5y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. pinia-plugin-persistedstate&amp;nbsp;설치하기 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785924898709&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install pinia-plugin-persistedstate --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E2GIt/dJMcaiEjgk7/zYaQrRwK4AyKT5zgF5QNVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E2GIt/dJMcaiEjgk7/zYaQrRwK4AyKT5zgF5QNVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E2GIt/dJMcaiEjgk7/zYaQrRwK4AyKT5zgF5QNVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE2GIt%2FdJMcaiEjgk7%2FzYaQrRwK4AyKT5zgF5QNVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;863&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\main.js 파일을 수정합니다. - pinia plugin를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785924947032&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue';
import { ref } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
const app = createApp(App);

:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\App.vue 파일을 수정합니다. - Storage를 제거합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785924991078&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { computed } from 'vue';
:

export default {
    name: 'AppView',
    setup() {
        :

        /*
        onMounted(()=&amp;gt; {
            loginStore.readStateFromStorage();
        });
        */

        :
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onMounted를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\stores\useLoginStore.js 파일을 수정합니다. - Storage를 제거합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785925043213&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:

export const useLoginStore = defineStore('login', () =&amp;gt; {
    :

    // 로그인합니다.
    async function doLogin(memberInfo) {
        :

        try {
            const res = await axios.post(&quot;http://127.0.0.1:9000/api/v1/member/login&quot;, memberInfo);
            if (res.data.code == 200) {
                :
                // Storage에서 state를 저장합니다.
                //saveStateToStorage();
                result = true;
            }
        }
        :
    }
    :

    // 로그아웃합니다.
    async function doLogout() {
        console.log(&quot;로그아웃되었습니다.&quot;);
        access_token.value = &quot;&quot;;
        refresh_token.value = &quot;&quot;;
        member_id.value = &quot;&quot;;
        member_name.value = &quot;&quot;;
        //localStorage.removeItem(&quot;lunchapp.login&quot;);
    }

    /*
    // Storage에서 state를 저장합니다.
    function saveStateToStorage() {
        const state = {
            access_token : access_token.value,
            refresh_token :  refresh_token.value,
            member_id : member_id.value,
            member_name : member_name.value
        }
        localStorage.setItem(&quot;lunchapp.login&quot;, JSON.stringify(state));
    }

    // Storage에서 state를 읽어옵니다.
    function readStateFromStorage() {
        if (localStorage.getItem(&quot;lunchapp.login&quot;) != null) {
            const state =  JSON.parse(localStorage.getItem(&quot;lunchapp.login&quot;));
            if (state.access_token != null) {
                access_token.value = state.access_token;
            }
            if (state.refresh_token != null) {
                refresh_token.value = state.refresh_token;
            }
            if (state.member_id != null) {
                member_id.value = state.member_id;
            }

            if (state.member_name != null) {
                member_name.value = state.member_name;
            }
        }
    }
    */

    return {
        access_token,
        refresh_token,
        member_id,
        member_name,
        isLogin,
        doLogin,
        isAccessTokenExpire,
        doRefreshToken,
        doLogout
        //readStateFromStorage
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. src\stores\useLoginStore.js 파일을 수정합니다. - 3번째 파라미터에 { persist:true }를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785925215813&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () =&amp;gt; {
    :
}, { persist:true });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 로그인을&amp;nbsp;하면&amp;nbsp;Application의&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;login으로&amp;nbsp;저장되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5BGeW/dJMcafU9pA5/WBdWK3Ka8JxKCcF4xvHcBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5BGeW/dJMcafU9pA5/WBdWK3Ka8JxKCcF4xvHcBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5BGeW/dJMcafU9pA5/WBdWK3Ka8JxKCcF4xvHcBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5BGeW%2FdJMcafU9pA5%2FWBdWK3Ka8JxKCcF4xvHcBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;Application의&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;저장된&amp;nbsp;login의&amp;nbsp;내용은&amp;nbsp;다음과&amp;nbsp;같습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key&amp;nbsp;:&amp;nbsp;login &lt;br /&gt;Value&amp;nbsp;:&amp;nbsp;{&quot;access_token&quot;:&quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwMzUyMzAsImV4cCI6MTc0NDAzNTI5MCwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiIwMWMzNWE1YWVlMmE0YjA1YjM0ZGQ4OWI1MmEwZDZhNiJ9.Smocyex5QHHNigkYh6OTbhqRUK2EtLzKT1oO8Mat3tU&quot;,&quot;refresh_token&quot;:&quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJSZWZyZXNoIiwic2NvcGUiOiJwcm9maWxlIHNjb3BlcyIsImlhdCI6MTc0NDAzNTIzMCwiZXhwIjoxNzQ0MDM1MzUwLCJhdWQiOiJsdW5jaG1lbnVBcHAiLCJpc3MiOiJhcGlzZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0MSIsImp0aSI6IjdhYWYyMzU0MTIzZDQxNGE5ZTU1YzdjZmQ4MmQyMzQzIn0.P8pc8rT52XZZCzGrB-WUC6pVRt5hMcZ_qD2A6XKkEB0&quot;,&quot;member_id&quot;:&quot;test1&quot;,&quot;member_name&quot;:&quot;테스트1&quot;} &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key는 Store의 ID입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Value는&amp;nbsp;Store의&amp;nbsp;State의&amp;nbsp;값으로&amp;nbsp;JSON&amp;nbsp;객체를&amp;nbsp;만들어&amp;nbsp;저장합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785925273638&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useLoginStore = defineStore('login', () =&amp;gt; {
    const access_token = ref(&quot;&quot;);
    const refresh_token = ref(&quot;&quot;);
    const member_id = ref(&quot;&quot;);
    const member_name = ref(&quot;&quot;);
    :
}, { persist:true });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 로그아웃을&amp;nbsp;하면&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;있는&amp;nbsp;login에&amp;nbsp;저장된&amp;nbsp;JSON&amp;nbsp;객체의&amp;nbsp;값이&amp;nbsp;제거되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDPYAP/dJMcabZxtsV/JdH848cN8xlMkAQZwp6DM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDPYAP/dJMcabZxtsV/JdH848cN8xlMkAQZwp6DM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDPYAP/dJMcabZxtsV/JdH848cN8xlMkAQZwp6DM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDPYAP%2FdJMcabZxtsV%2FJdH848cN8xlMkAQZwp6DM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;&lt;b&gt;Pinia Plugin Persistedstate 구성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;key : Storage의 key를 설정합니다.&amp;nbsp;&amp;nbsp;(기본값: store.$id)&lt;/li&gt;
&lt;li&gt;storage : 저장소(localStorage, sessionStorage)를 설정합니다. (기본값: localStorage)&lt;/li&gt;
&lt;li&gt;serializer : 저장하기전에 직렬화하고 가져올 때 역직렬화를 설정합니다. (기본값: JSON.stringify/destr)&lt;/li&gt;
&lt;li&gt;pick : 선택적으로 저장할 state를 선택합니다. (기본값: 없음, 전체 state가 저장됩니다.)&lt;/li&gt;
&lt;li&gt;omit : 선택적으로 생략할 state를 선택합니다. (기본값: 없음)&lt;/li&gt;
&lt;li&gt;beforeHydrate : Storage에서 가져오기 전에 호출되는 Callback 함수를 설정합니다. (기본값: 없음)&lt;/li&gt;
&lt;li&gt;afterHydrate&amp;nbsp;:&amp;nbsp;Storage에서&amp;nbsp;가져온&amp;nbsp;후에&amp;nbsp;호출되는&amp;nbsp;Callback&amp;nbsp;함수를&amp;nbsp;설정합니다.&amp;nbsp;(기본값:&amp;nbsp;없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores\useLoginStore.js 파일을 수정합니다. - 3번째 파라미터에 persist를 구성하여 추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785925406435&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () =&amp;gt; {
    :
}, { persist: {
        key: 'lunchapp.login',
        storage: localStorage,
        pick: ['access_token'],
        beforeHydrate:(ctx =&amp;gt; console.log(`about to hydrate ${ctx.store.$id}`)),
        afterHydrate:(ctx =&amp;gt; console.log(`just hydrated ${ctx.store.$id}`))
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localStorage에 lunchapp.login이란 Key로 access_token만 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storage에서&amp;nbsp;가져오기&amp;nbsp;전과&amp;nbsp;후에&amp;nbsp;콘솔로&amp;nbsp;로그를&amp;nbsp;출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 로그인을&amp;nbsp;하면&amp;nbsp;Application의&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;lunchapp.login으로&amp;nbsp;저장되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3m72Y/dJMcaccZey6/kGV8OuZ4QjITsgL0bGQ601/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3m72Y/dJMcaccZey6/kGV8OuZ4QjITsgL0bGQ601/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3m72Y/dJMcaccZey6/kGV8OuZ4QjITsgL0bGQ601/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3m72Y%2FdJMcaccZey6%2FkGV8OuZ4QjITsgL0bGQ601%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;3. Application의&amp;nbsp;Local&amp;nbsp;storage에&amp;nbsp;저장된&amp;nbsp;lunchapp.login의&amp;nbsp;내용은&amp;nbsp;다음과&amp;nbsp;같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key&amp;nbsp;:&amp;nbsp;lunchapp.login &lt;br /&gt;Value&amp;nbsp;:&amp;nbsp;{&quot;access_token&quot;:&quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwNDEyMzIsImV4cCI6MTc0NDA0MTI5MiwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiJlMWYyM2U1YjcyMTA0NmM0OWNiYWM2YzMyMDExM2EzNSJ9.JLQ_MdFngg0-0dN_Ncx885ELzmqPba0EZUiXSOWG06M&quot;} &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트로&amp;nbsp;access_token만&amp;nbsp;저장되기&amp;nbsp;때문에&amp;nbsp;로그인이&amp;nbsp;유지되지&amp;nbsp;않습니다. &lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>cookies</category>
      <category>localstorage</category>
      <category>Pinia Plugin Persistedstate</category>
      <category>Store State</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/342</guid>
      <comments>https://carrotweb.tistory.com/342#entry342comment</comments>
      <pubDate>Wed, 5 Aug 2026 19:28:38 +0900</pubDate>
    </item>
    <item>
      <title>[17] Frontend 개발 - Login 관리, 모듈화</title>
      <link>https://carrotweb.tistory.com/341</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;모듈화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios&amp;nbsp;Instance&amp;nbsp;(엑시오스&amp;nbsp;인터셉터) &lt;br /&gt;(&lt;a href=&quot;https://axios-http.com/kr/docs/instance&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://axios-http.com/kr/docs/instance&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q2gri/dJMcacjIX46/SkijwRxDNh0FWnjKkHMBYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q2gri/dJMcacjIX46/SkijwRxDNh0FWnjKkHMBYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q2gri/dJMcacjIX46/SkijwRxDNh0FWnjKkHMBYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ2gri%2FdJMcacjIX46%2FSkijwRxDNh0FWnjKkHMBYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;837&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\apis\axiosInterceptor.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785922214204&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from 'axios';
import { useLoginStore } from '@/stores/useLoginStore';
import { useRouter } from 'vue-router';

export const createAxiosInstance = (baseURL, timeout) =&amp;gt; {
    const loginStore = useLoginStore();
    
    const router = useRouter();

    const axiosInstance = axios.create({
        baseURL : baseURL,
        timeout : timeout
    });
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;baseURL은 기본 URL입니다. baseURL이 설정되어 있으면 메소드의 url과 합쳐집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timeout은&amp;nbsp;요청에&amp;nbsp;대한&amp;nbsp;응답이&amp;nbsp;길어질&amp;nbsp;경우&amp;nbsp;중단을&amp;nbsp;하기&amp;nbsp;위한&amp;nbsp;시간입니다.&amp;nbsp;(timeout&amp;nbsp;시간을&amp;nbsp;짧게&amp;nbsp;하면&amp;nbsp;서버&amp;nbsp;timeout(응답&amp;nbsp;코드&amp;nbsp;408,&amp;nbsp;timeout))&amp;nbsp;전에도&amp;nbsp;중단할&amp;nbsp;수&amp;nbsp;있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\apis에&amp;nbsp;lunchmenuApi.js&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccHD2m/dJMcab6e088/6j5sKhyxUzg2iagJBjtTLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccHD2m/dJMcab6e088/6j5sKhyxUzg2iagJBjtTLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccHD2m/dJMcab6e088/6j5sKhyxUzg2iagJBjtTLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccHD2m%2FdJMcab6e088%2F6j5sKhyxUzg2iagJBjtTLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;825&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785922268692&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createAxiosInstance } from './axiosInterceptor';

let axiosInstance = null;

// LunchmenuApi를 위한 Axios 인스턴스 생성
function createLunchmenuApi() {
    if (axiosInstance == null) {
        axiosInstance = createAxiosInstance(&quot;http://127.0.0.1:9000&quot;, 60);
    }
    return axiosInstance;
}

// 전체 점심 메뉴 리스트를 가져옵니다.
export function getLunchMenuList() {
    return createLunchmenuApi().get(&quot;/api/v1/lunchmenus/&quot;);
}

// 점심 메뉴 번호로 검색하여 점심 메뉴를 가져옵니다.
export function getLunchMenuItem(seq) {
    return createLunchmenuApi().get(&quot;/api/v1/lunchmenus/&quot; + seq);
}

// 점심 메뉴를 추가합니다.
export function addLunchMenuItem(lunchMenuItem) {
    return createLunchmenuApi().post(&quot;/api/v1/lunchmenus/&quot;, lunchMenuItem);
}

// 점심 메뉴를 수정합니다.
export function editLunchMenuItem(seq, lunchMenuItem) {
    return createLunchmenuApi().put(&quot;/api/v1/lunchmenus/&quot; + seq, lunchMenuItem);
}

// 점심 메뉴를 삭제합니다.
export async function removeLunchMenuItem(seq) {
    return createLunchmenuApi().delete(&quot;/api/v1/lunchmenus/&quot; + seq);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\views\LunchMenu\LunchMenuListView.vue 파일을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785922450226&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
// import { createAxiosInstance } from '@/apis/axiosInterceptor';
import * as lunchmenuApi from '@/apis/lunchmenuApi';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        :
        //const loginStore = useLoginStore();
        :

         async function getLunchMenuList() {
            try {
                //const res = await createAxiosInstance(&quot;http://127.0.0.1:9000&quot;).get(&quot;/api/v1/lunchmenus/&quot;);
                const res = await lunchmenuApi.getLunchMenuList();
                console.log(res);
                lunchMenuList.value = res.data.data;
            } catch(err) {
                console.log(err);
            }
        }
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\views\LunchMenu\LunchMenuAddView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785922483301&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
//import axios from 'axios';
import * as lunchmenuApi from '@/apis/lunchmenuApi';
:

export default {
    name : 'LunchMenuAddView',
    setup() {
        :
        function addClick() {
            const lunchMenuItem = { menuName : state.lunchMenuName };
            //axios.post(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, lunchMenuItem).then(() =&amp;gt; {
            lunchmenuApi.addLunchMenuItem(lunchMenuItem).then(() =&amp;gt; {
                router.replace({name : 'lunchmenulist'});
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }

        function getLunchMenuItem() {
            //axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot; + route.query.seq).then((res) =&amp;gt; {
            lunchmenuApi.getLunchMenuItem(route.query.seq).then((res) =&amp;gt; {
                state.lunchMenuName = res.data.data.menu_name;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }

        function editClick() {
            if (state.lunchMenuName == &quot;&quot;) {
                alert(&quot;점심 메뉴 명을 입력하세요.&quot;);
                lunchMenuNameInput.value.focus();
                return;
            }
            var result = confirm(&quot;수정하시겠습니까?&quot;);
            if (result) {
                const lunchMenuItem = { menuName : state.lunchMenuName };
                //axios.put(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;+ route.query.seq, lunchMenuItem).then(() =&amp;gt; {
                lunchmenuApi.editLunchMenuItem(route.query.seq, lunchMenuItem).then(() =&amp;gt; {
                    router.replace({name : 'lunchmenuread', query : {seq : route.query.seq}});
                }).catch((err) =&amp;gt; {
                    console.log(err);
                });
            }
        }
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. src\views\LunchMenu\LunchMenuReadView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785922610653&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
//import axios from 'axios';
import * as lunchmenuApi from '@/apis/lunchmenuApi';
:

export default {
    name : 'LunchMenuReadView',
    setup() {
        :
         function getLunchMenuItem() {
            //axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot; + route.query.seq).then((res) =&amp;gt; {
            lunchmenuApi.getLunchMenuItem(route.query.seq).then((res) =&amp;gt; {
                console.log(res);
                lunchMenuItem.value = res.data.data;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }
        :

        async function deleteClick() {
            var result = confirm(&quot;삭제하시겠습니까?&quot;);
            if (result) {
                try {
                    //const res = await axios.delete(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot; + route.query.seq);
                    const res = await lunchmenuApi.removeLunchMenuItem(route.query.seq);
                    console.log(res);
                    if (res.data.code == 200) {
                        alert(&quot;삭제되었습니다.&quot;);
                        router.push({name : 'lunchmenulist'});
                    } else {
                        alert(&quot;삭제되지 않았습니다.&quot;);
                    }
                } catch(err) {
                    console.log(err);
                }
            }
        }
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;로그인 /로그아웃, 로그인 여부 확인&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores\useLoginStore.js&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785922743675&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () =&amp;gt; {
    const access_token = ref(&quot;&quot;);
    const refresh_token = ref(&quot;&quot;);
    const member_id = ref(&quot;&quot;);
    const member_name = ref(&quot;&quot;);

    // 로그인 여부를 가져옵니다.
    /*
    const isLogin = computed(() =&amp;gt; {
        return refresh_token.value == &quot;&quot; ? false : true;
    });
    */
    function isLogin() {
        let login = false;
        if (refresh_token.value != &quot;&quot;) {
            // refresh_token에서 .(도트)로 분리하여 payload를 가져옵니다.
            let base64Payload = refresh_token.value.split('.')[1];
            // URL과 호환되지 않는 문자를 base64 표준 문자로 교체합니다.
            base64Payload = base64Payload.replace(/-/g, '+').replace(/_/g, '/');
            // atob() 메소드로 복호화합니다.
            base64Payload = atob(base64Payload);
            // JSON 객체로 변환합니다.
            const payloadObject = JSON.parse(base64Payload);
            // refresh_token의 만료 시간을 확인합니다.
            const currentDate = new Date().getTime() / 1000;
            if (payloadObject.exp &amp;lt;= currentDate) {
                console.log(&quot;token expired&quot;);
            } else {
                console.log(&quot;token valid&quot;);
                login = true;
            }
        }
        return login;
    }

    // 로그인합니다.
    async function doLogin(memberInfo) {
        let result = false;
        let resultErr = null;

        try {
            const res = await axios.post(&quot;http://127.0.0.1:9000/api/v1/member/login&quot;, memberInfo);
            if (res.data.code == 200) {
                console.log(&quot;로그인되었습니다.&quot;);
                access_token.value = res.data.access_token;
                refresh_token.value = res.data.refresh_token;
                // access_token에서 .(도트)로 분리하여 payload를 가져옵니다.
                let base64Payload = access_token.value.split('.')[1];
                // URL과 호환되지 않는 문자를 base64 표준 문자로 교체합니다.
                base64Payload = base64Payload.replace(/-/g, '+').replace(/_/g, '/');
                // atob() 메소드로 복호화합니다.
                base64Payload = atob(base64Payload);
                // JSON 객체로 변환합니다.
                const payloadObject = JSON.parse(base64Payload);
                member_id.value = payloadObject.sub;
                member_name.value = decodeURIComponent(escape(payloadObject.name));
                result = true;
            }
        } catch(err) {
            :

    // 로그아웃합니다.
    async function doLogout() {
        console.log(&quot;로그아웃되었습니다.&quot;);
        access_token.value = &quot;&quot;;
        refresh_token.value = &quot;&quot;;
        member_id.value = &quot;&quot;;
        member_name.value = &quot;&quot;;
    }

    return {
        access_token,
        refresh_token,
        member_id,
        member_name,
        isLogin,
        doLogin,
        isAccessTokenExpire,
        doRefreshToken,
        doLogout
    }
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;computed를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\router\index.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785922898747&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:

const Authentication = () =&amp;gt; (to, from, next) =&amp;gt; {
  const loginStore = useLoginStore();
  if (to.name.startsWith(&quot;lunchmenu&quot;)) {
    if (!loginStore.isLogin()) {
      next({path: '/login', query: { returnUrl: to.fullPath }});
    } else {
      next();
    }
  } else {
    next();
  }
};

:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\App.vue&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785922956522&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;header&quot;&amp;gt;
        &amp;lt;div class=&quot;topline&quot;&amp;gt;
            &amp;lt;div class=&quot;headmenu&quot;&amp;gt;
                &amp;lt;div v-if=&quot;isLogin()&quot;&amp;gt;
                    {{greetingMessage}}
                    &amp;lt;span @click=&quot;Logout()&quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div v-else&amp;gt;
                    &amp;lt;router-link :to=&quot;{ name: 'Login' }&quot;&amp;gt;로그인&amp;lt;/router-link&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;nav&amp;gt;
            &amp;lt;router-link to=&quot;/&quot;&amp;gt;Home&amp;lt;/router-link&amp;gt; |
            &amp;lt;router-link to=&quot;/about&quot;&amp;gt;About&amp;lt;/router-link&amp;gt; |
            &amp;lt;router-link to=&quot;/lunchmenu/list&quot;&amp;gt;LunchMenu&amp;lt;/router-link&amp;gt;
        &amp;lt;/nav&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;router-view/&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { computed } from 'vue';
import { useLoginStore } from '@/stores/useLoginStore';
import { useRouter } from 'vue-router';

export default {
  name: 'AppView',
  setup() {
    const loginStore = useLoginStore();
    const router = useRouter();

    // 로그인 여부를 가져옵니다.
    function isLogin() {
      return loginStore.isLogin();
    }

    // 로그아웃합니다
    function Logout() {
      loginStore.doLogout();
      router.replace({name : 'home'});
    }

    // 환영 메시지를 가져옵니다.
    const greetingMessage = computed(() =&amp;gt; {
      return `${loginStore.member_name}님, 안녕하세요.`;
    });

    return {
      isLogin,
      Logout,
      greetingMessage
    }
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
nav {
  padding: 30px;
}
nav a {
  font-weight: bold;
  color: #2c3e50;
}
nav a.router-link-exact-active {
  color: #42b983;
}
.header {
  position:relative;
}
.header .topline {
  position:relative;
  height:30px;
  margin:0 10px;
}
.header .topline .headmenu {
  position:absolute;
  right:0;
  top:4px;
}
.header .topline .headmenu a {
  font-weight:bold;
  color:#2c3e50;
}
.header .topline .headmenu span {
  font-weight:bold;
  color:#2c3e50;
  cursor:pointer;
  text-decoration:underline;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 웹 브라우저에서 확인하기 (http://localhost:8080) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;larr; 로그인 전&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXnqaw/dJMcabrDmeK/1VXtIgVBmLaNykHf0Bjq01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXnqaw/dJMcabrDmeK/1VXtIgVBmLaNykHf0Bjq01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXnqaw/dJMcabrDmeK/1VXtIgVBmLaNykHf0Bjq01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXnqaw%2FdJMcabrDmeK%2F1VXtIgVBmLaNykHf0Bjq01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) &amp;larr; 로그인 후&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXkovq/dJMcagzGCeo/L5lUenNsxasjWkAthduFk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXkovq/dJMcagzGCeo/L5lUenNsxasjWkAthduFk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXkovq/dJMcagzGCeo/L5lUenNsxasjWkAthduFk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXkovq%2FdJMcagzGCeo%2FL5lUenNsxasjWkAthduFk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>axios instance</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/341</guid>
      <comments>https://carrotweb.tistory.com/341#entry341comment</comments>
      <pubDate>Wed, 5 Aug 2026 18:50:22 +0900</pubDate>
    </item>
    <item>
      <title>[16] Frontend 개발 - Login 관리, Axios Interceptor</title>
      <link>https://carrotweb.tistory.com/340</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Axios Interceptor (엑시오스 인터셉터)를 사용하여 Access Token 재발급(갱신)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios&amp;nbsp;Interceptor&amp;nbsp;(엑시오스&amp;nbsp;인터셉터) &lt;br /&gt;(&lt;a href=&quot;https://axios-http.com/kr/docs/interceptors&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://axios-http.com/kr/docs/interceptors&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JhuSV/dJMcaa0xJcJ/7dKLdzpyKR6KOaUawKB8m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JhuSV/dJMcaa0xJcJ/7dKLdzpyKR6KOaUawKB8m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JhuSV/dJMcaa0xJcJ/7dKLdzpyKR6KOaUawKB8m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJhuSV%2FdJMcaa0xJcJ%2F7dKLdzpyKR6KOaUawKB8m1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios&amp;nbsp;요청&amp;nbsp;전이나&amp;nbsp;Axios&amp;nbsp;응답&amp;nbsp;후&amp;nbsp;처리는&amp;nbsp;Axios&amp;nbsp;Interceptor&amp;nbsp;(엑시오스&amp;nbsp;인터셉터)을&amp;nbsp;사용하여&amp;nbsp;가로챌&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios&amp;nbsp;Interceptor&amp;nbsp;(엑시오스&amp;nbsp;인터셉터)&amp;nbsp;기본&amp;nbsp;구조입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785916240452&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 요청 인터셉터
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    // 요청이 전달되기 전에 처리
    return config;
  }, function (error) {
    // Do something with request error
    // 요청 오류에 대한 처리
    return Promise.reject(error);
  });

// 응답 인터셉터
axios.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // 응답 상태코드가 2xx 범위안에 있으면 트리거됩니다.
    // Do something with response data
    // 응답 데이터로 처리
    return response;
  }, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // 응답 상태코드가 2xx 범위를 벗어나면 트리거됩니다.
    // Do something with response error
    // 응답 오류에 대한 처리
    return Promise.reject(error);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src에&amp;nbsp;apis&amp;nbsp;디렉터리를&amp;nbsp;생성하고&amp;nbsp;axiosInterceptor.js&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yYfU1/dJMcacqBr3N/MkoTLh4MFvfq0llcN6gut1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yYfU1/dJMcacqBr3N/MkoTLh4MFvfq0llcN6gut1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yYfU1/dJMcacqBr3N/MkoTLh4MFvfq0llcN6gut1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyYfU1%2FdJMcacqBr3N%2FMkoTLh4MFvfq0llcN6gut1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;825&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785916288287&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from 'axios';

const axiosInstance = axios.create();

// 요청 인터셉터
axiosInstance.interceptors.request.use(function (config) {
    console.log(&quot;axios.interceptors.request&quot;);
    return config;
  }, function (error) {
    console.log(&quot;axios.interceptors.request.error&quot;);
    console.log(error);
    return Promise.reject(error);
  });

// 응답 인터셉터
axiosInstance.interceptors.response.use(function (response) {
    console.log(&quot;axios.interceptors.response&quot;);
    return response;
  }, function (error) {
    console.log(&quot;axios.interceptors.response.error&quot;);
    console.log(error);
    return Promise.reject(error);
  });

  export default axiosInstance;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vkE9C/dJMb99URjHH/6XIAgTkaw4gKiSFCSDFf31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vkE9C/dJMb99URjHH/6XIAgTkaw4gKiSFCSDFf31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vkE9C/dJMb99URjHH/6XIAgTkaw4gKiSFCSDFf31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvkE9C%2FdJMb99URjHH%2F6XIAgTkaw4gKiSFCSDFf31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;3. src\apis\axiosInterceptor.js 파일을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785916407321&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from 'axios';
import { useLoginStore } from '@/stores/useLoginStore';

export const createAxiosInstance = () =&amp;gt; {
    const loginStore = useLoginStore();
    
    const axiosInstance = axios.create();

    // 요청 인터셉터
    axiosInstance.interceptors.request.use(async function (config) {
        console.log(&quot;axios.interceptors.request&quot;);
        console.log(loginStore.access_token);
        return config;
    }, function (error) {
        console.log(&quot;axios.interceptors.request.error&quot;);
        console.log(error);
        return Promise.reject(error);
    });

    // 응답 인터셉터
    axiosInstance.interceptors.response.use(function (response) {
        console.log(&quot;axios.interceptors.response&quot;);
        return response;
    }, function (error) {
        console.log(&quot;axios.interceptors.response.error&quot;);
        console.log(error);
        return Promise.reject(error);
    });

    return axiosInstance;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store를&amp;nbsp;사용하기&amp;nbsp;위해서&amp;nbsp;함수로&amp;nbsp;변경해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785916426791&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
//import axios from 'axios';
import { createAxiosInstance } from '@/apis/axiosInterceptor';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        const router = useRouter();
        const route = useRoute();
        :

        async function getLunchMenuList() {
            console.log(loginStore.access_token);
            :

            try {
                const res = await createAxiosInstance().get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, {
                    headers: {
                        &quot;Authorization&quot; : &quot;Bearer &quot; + loginStore.access_token
                    }
                });
                console.log(res);
                lunchMenuList.value = res.data.data;
            } catch(err) {
                console.log(err);
            }
            :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;웹 브라우저에서 확인하기 (&lt;/span&gt;http://localhost:8080/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eBfixW/dJMcadiJXVg/xMbeWkVEqZTVSSbKVe5G0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eBfixW/dJMcadiJXVg/xMbeWkVEqZTVSSbKVe5G0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eBfixW/dJMcadiJXVg/xMbeWkVEqZTVSSbKVe5G0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeBfixW%2FdJMcadiJXVg%2FxMbeWkVEqZTVSSbKVe5G0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;&lt;b&gt;Axios Interceptor (엑시오스 인터셉터) 사용 - Axios 요청 전 적용&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;Access&amp;nbsp;Token의&amp;nbsp;만료&amp;nbsp;확인&amp;nbsp;시점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청하기 전에 Access Token이 만료되었는지 확인하기 위해서는 Axios Interceptor (엑시오스 인터셉터)을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token의 payload에 있는 exp(expiration, 만료 시간)를 확인하여 만료되었는지 확인 후 Refresh Token를 사용하여 Access Token를 재발급합니다. &amp;rarr; 매번 Access Token를 확인&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/snSGj/dJMcaiEjdWx/Pr1gBKXZLA0du2cqUdbfk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/snSGj/dJMcaiEjdWx/Pr1gBKXZLA0du2cqUdbfk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/snSGj/dJMcaiEjdWx/Pr1gBKXZLA0du2cqUdbfk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsnSGj%2FdJMcaiEjdWx%2FPr1gBKXZLA0du2cqUdbfk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;241&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\apis\axiosInterceptor.js 파일을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785920516797&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import { useRouter } from 'vue-router';

export const createAxiosInstance = () =&amp;gt; {
    :
    const router = useRouter();
    :

    // 요청 인터셉터
    axiosInstance.interceptors.request.use(async function (config) {
        console.log(&quot;axios.interceptors.request&quot;);
        console.log(loginStore.access_token);
        if (loginStore.isAccessTokenExpire()) {
            console.log(&quot;Access Token이 만료되었습니다.&quot;);
            try {
                await loginStore.doRefreshToken();
            } catch(err) {
                console.log(err);
                alert(&quot;인증이 만료되었습니다.\n다시 로그인 해주시기 바랍니다.&quot;);
                router.replace({name : 'Login'});
            }
        }
        return config;
    }, function (error) {
        console.log(&quot;axios.interceptors.request.error&quot;);
        console.log(error);
        return Promise.reject(error);
    });
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785920564684&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import { useRouter } from 'vue-router';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        :
        // const route = useRoute();
        :

        async function getLunchMenuList() {
            console.log(loginStore.access_token);
            /*
            if (loginStore.isAccessTokenExpire()) {
                console.log(&quot;Access Token이 만료되었습니다.&quot;);
                try {
                    await loginStore.doRefreshToken();
                } catch(err) {
                    console.log(err);
                    alert(&quot;인증이 만료되었습니다.\n다시 로그인 해주시기 바랍니다.&quot;);
                    router.replace({path: '/login', query: { returnUrl: route.fullPath }});
                    return;
                }
            }
            */

            :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRoute를 삭제하고 Access Token 만료 처리를 부분을 주석처리하거나 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\views\Account\LoginView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785920699656&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:

export default {
    name : 'LoginView',
    setup() {
        :
        function loginClick() {
            :

            const memberInfo = { id: state.memberId, password: state.memberPassword };
            loginStore.doLogin(memberInfo).then(() =&amp;gt; {
                const returnUrl = route.query.returnUrl;
                if (returnUrl == null || returnUrl == &quot;&quot;) {
                    router.replace({name : 'home'});
                } else {
                    router.replace({path : returnUrl});
                }
            }).catch((err) =&amp;gt; {
                state.errorMessage = err.message;
            });
        }
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;returnUrl이&amp;nbsp;없으면&amp;nbsp;홈으로&amp;nbsp;이동되게&amp;nbsp;처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Access Token 만료 (10분 후)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Access&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; Token 재발급(갱신)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXnUrI/dJMcafOqhDI/VRWkrj7VU1jby1QWUPf2UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXnUrI/dJMcafOqhDI/VRWkrj7VU1jby1QWUPf2UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXnUrI/dJMcafOqhDI/VRWkrj7VU1jby1QWUPf2UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXnUrI%2FdJMcafOqhDI%2FVRWkrj7VU1jby1QWUPf2UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;837&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Access Token 만료 (15분 후) &amp;rarr; Refresh Token 만료 &amp;rarr; 재발급(갱신) 불가 &amp;rarr; 로그인 &amp;rarr; 홈&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBPKTu/dJMcafOqhFu/py2FITnZziJCvkpWQBEbK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBPKTu/dJMcafOqhFu/py2FITnZziJCvkpWQBEbK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBPKTu/dJMcafOqhFu/py2FITnZziJCvkpWQBEbK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBPKTu%2FdJMcafOqhFu%2Fpy2FITnZziJCvkpWQBEbK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;&lt;b&gt;Axios Interceptor (엑시오스 인터셉터) 사용 - Axios&amp;nbsp;응답&amp;nbsp;후&amp;nbsp;적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Access&amp;nbsp;Token의&amp;nbsp;만료&amp;nbsp;확인&amp;nbsp;시점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 후 응답에서 Access Token이 만료되었는지 확인하기 위해서는 Axios Interceptor (엑시오스 인터셉터)을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 후 응답에서 Access Token이 만료되었다는 응답 코드(401, Unauthorized)와 응답 메시지(&amp;ldquo;토큰이 만료되었습니다.&amp;rdquo;)가 오면 Refresh Token를 사용하여 Access Token를 재발급하고 다시 요청을 보냅니다. &amp;rarr; 요청에 대한 응답이 실패일 경우만 처리 &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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXg2zX/dJMcadv5TH1/PKkscVXiizTgmmuj7uItK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXg2zX/dJMcadv5TH1/PKkscVXiizTgmmuj7uItK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXg2zX/dJMcadv5TH1/PKkscVXiizTgmmuj7uItK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXg2zX%2FdJMcadv5TH1%2FPKkscVXiizTgmmuj7uItK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;309&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\apis\axiosInterceptor.js 파일을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785921298503&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:

export const createAxiosInstance = () =&amp;gt; {
    :

    // 요청 인터셉터
    axiosInstance.interceptors.request.use(async function (config) {
        console.log(&quot;axios.interceptors.request&quot;);
        console.log(loginStore.access_token);

        config.headers['Content-Type'] = 'application/json';
        config.headers['Authorization'] = `Bearer ${loginStore.access_token}`;

        /*
        if (loginStore.isAccessTokenExpire()) {
            console.log(&quot;Access Token이 만료되었습니다.&quot;);
            try {
                await loginStore.doRefreshToken();
            } catch(err) {
                console.log(err);
                alert(&quot;인증이 만료되었습니다.\n다시 로그인 해주시기 바랍니다.&quot;);
                router.replace({name : 'Login'});
            }
        }
        */

        return config;
    }, function (error) {
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Request&amp;nbsp;Header에&amp;nbsp;Authorization를&amp;nbsp;추가하고&amp;nbsp;Access&amp;nbsp;Token&amp;nbsp;만료&amp;nbsp;처리&amp;nbsp;부분을&amp;nbsp;주석처리하거나&amp;nbsp;삭제합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785921372551&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
//import { useLoginStore } from '@/stores/useLoginStore';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        :
        //const loginStore = useLoginStore();
        :

        async function getLunchMenuList() {
            //console.log(loginStore.access_token);

            try {
            	/*
                const res = await createAxiosInstance().get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, {
                    headers: {
                        &quot;Authorization&quot; : &quot;Bearer &quot; + loginStore.access_token
                    }
                });
                */
                const res = await createAxiosInstance().get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;);
                console.log(res);
                lunchMenuList.value = res.data.data;
            } catch(err) {
                console.log(err);
            }
        }
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Header를&amp;nbsp;삭제합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\apis\axiosInterceptor.js&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785921602325&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:

export const createAxiosInstance = () =&amp;gt; {
    :

    // 응답 인터셉터
    axiosInstance.interceptors.response.use(function (response) {
        console.log(&quot;axios.interceptors.response&quot;);
        return response;
    }, async function (error) {
        console.log(&quot;axios.interceptors.response.error&quot;);
        console.log(error);
        var axiosRequest = error.config;
        if (error.response &amp;amp;&amp;amp; !axiosRequest._retry) {
            axiosRequest._retry = true;
            // 응답코드가 401이면 처리합니다.
            if (error.response.status == 401) {
                if (loginStore.isAccessTokenExpire()) {
                    console.log(&quot;Access Token이 만료되었습니다.&quot;);
                    try {
                        // Access Token 재발급 요청
                        await loginStore.doRefreshToken();

                        // 재발급된 Access Token으로 header를 수정합니다.
                        axiosRequest.headers = {
                            'Content-Type': 'application/json',
                            Authorization: `Bearer ${loginStore.access_token}`
                          };
                        // 다시 요청(request)합니다.
                        const response = await axios.request(axiosRequest);
                        return response;
                    } catch(err) {
                        console.log(err);
                        alert(&quot;인증이 만료되었습니다.\n다시 로그인 해주시기 바랍니다.&quot;);
                        router.replace({name : 'Login'});
                    }
                }
            }
        }
        return Promise.reject(error);
    });
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재요청이 실패하면 계속 반복되기 때문에 반복을 방지하기 위해서 요청 정보(config)에 _retry를 사용하여 한 번만 처리되게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 상태코드가 401이고 Access Token 만료되어 있으면 Access Token를 재발급하고 요청 정보(config)를 사용하여 다시 요청합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Access Token 만료 (10분 후)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Access&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Token 재발급(갱신)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oTZRQ/dJMcaaTMpWA/GV4h0zIFkzEiXrQjjz5x5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oTZRQ/dJMcaaTMpWA/GV4h0zIFkzEiXrQjjz5x5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oTZRQ/dJMcaaTMpWA/GV4h0zIFkzEiXrQjjz5x5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoTZRQ%2FdJMcaaTMpWA%2FGV4h0zIFkzEiXrQjjz5x5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Access Token 만료 (15분 후) &amp;rarr; Refresh Token 만료 &amp;rarr; 재발급(갱신) 불가 &amp;rarr; 로그인 &amp;rarr; 홈&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P5vFv/dJMcagzGBcd/VwNKJKbtzS7aZkZacpWE00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P5vFv/dJMcagzGBcd/VwNKJKbtzS7aZkZacpWE00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P5vFv/dJMcagzGBcd/VwNKJKbtzS7aZkZacpWE00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP5vFv%2FdJMcagzGBcd%2FVwNKJKbtzS7aZkZacpWE00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>axios interceptor</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/340</guid>
      <comments>https://carrotweb.tistory.com/340#entry340comment</comments>
      <pubDate>Wed, 5 Aug 2026 18:29:16 +0900</pubDate>
    </item>
    <item>
      <title>[15] Frontend 개발 - Login 관리, Access Token, Refresh Token</title>
      <link>https://carrotweb.tistory.com/339</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Access&amp;nbsp;Token,&amp;nbsp;Refresh&amp;nbsp;Token&amp;nbsp;(액세스&amp;nbsp;토큰,&amp;nbsp;리프레시&amp;nbsp;토큰)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores\useLoginStore.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785915074542&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () =&amp;gt; {
    const access_token = ref(&quot;&quot;);
    const refresh_token = ref(&quot;&quot;);

    // 로그인 여부를 가져옵니다.
    const isLogin = computed(() =&amp;gt; {
        return refresh_token.value == &quot;&quot; ? false : true;
    });

    // 로그인합니다.
    async function doLogin(memberInfo) {
        let result = false;
        let resultErr = null;

        try {
            const res = await axios.post(&quot;http://127.0.0.1:9000/api/v1/member/login&quot;, memberInfo);
            if (res.data.code == 200) {
                console.log(&quot;로그인되었습니다.&quot;);
                access_token.value = res.data.access_token;
                refresh_token.value = res.data.refresh_token;
                result = true;
            }
        } catch(err) {
            // 400, 401, 500 에러 처리
            console.log(err);
            if (!err.response) {
                err.response = {
                    data:{&quot;result&quot;:&quot;fail&quot;, &quot;code&quot;:401, &quot;message&quot;:&quot;사용자가 없습니다.&quot;, access_token: &quot;&quot;, refresh_token: &quot;&quot;}
                };
            }
            resultErr = err.response.data;
        }

        return new Promise((resolve, reject) =&amp;gt; {
            if (result) {
                resolve();
            } else {
                reject(resultErr);
            }
        });
    }
  
    return {
        access_token,
        refresh_token,
        isLogin,
        doLogin
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785915276507&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:

export default {
    name : 'LunchMenuListView',
    setup() {
        :

        function getLunchMenuList() {
            console.log(loginStore.access_token);
            axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, {
                headers: {
                    &quot;Authorization&quot; : &quot;Bearer &quot; + loginStore.access_token
                }
            }).then((res) =&amp;gt; {
                console.log(res);
                lunchMenuList.value = res.data.data;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }
        :
    }
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAjtkX/dJMcafOqb6K/OKHtkiDPkpzIL7mk4laG10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAjtkX/dJMcafOqb6K/OKHtkiDPkpzIL7mk4laG10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAjtkX/dJMcafOqb6K/OKHtkiDPkpzIL7mk4laG10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAjtkX%2FdJMcafOqb6K%2FOKHtkiDPkpzIL7mk4laG10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;JWT&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;1359&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YXJfs/dJMcad3ZaAq/KegAbxoUAl0yWSOcr8KKjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YXJfs/dJMcad3ZaAq/KegAbxoUAl0yWSOcr8KKjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YXJfs/dJMcad3ZaAq/KegAbxoUAl0yWSOcr8KKjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYXJfs%2FdJMcad3ZaAq%2FKegAbxoUAl0yWSOcr8KKjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;877&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iat(Issued At Time, 발급된 시간)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exp(Expiration&amp;nbsp;Time,&amp;nbsp;만료&amp;nbsp;시간) &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Access&amp;nbsp;Token&amp;nbsp;재발급(갱신)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) &amp;rarr; Access Token 만료 (10분 후)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R0ly9/dJMcaf8x6bx/PRGePnJvyUEM1jVWkV9vsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R0ly9/dJMcaf8x6bx/PRGePnJvyUEM1jVWkV9vsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R0ly9/dJMcaf8x6bx/PRGePnJvyUEM1jVWkV9vsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR0ly9%2FdJMcaf8x6bx%2FPRGePnJvyUEM1jVWkV9vsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;b&gt;Access Token의 만료 확인 시점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token을 사용할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청하기 전에 Access Token이 만료되었는지 확인 후 Refresh Token를 사용하여 Access Token이 재발급 &amp;rarr; Access Token의 payload에 있는 exp(expiration, 만료 시간)로 확인 &amp;rarr; 매번 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 후 응답에서 Access Token이 만료되었는지 확인 후 Refresh Token를 사용하여 Access Token이 재발급 &amp;rarr; 응답 코드와 응답 메시지로 확인 &amp;rarr; 실패일 경우만 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores\useLoginStore.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&amp;nbsp;-&amp;nbsp;Access&amp;nbsp;Token&amp;nbsp;만료&amp;nbsp;확인과&amp;nbsp;재발급(갱신)&amp;nbsp;함수&amp;nbsp;추가 &lt;/p&gt;
&lt;pre id=&quot;code_1785915661090&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
    // Access Token이 만료되었는지 확인합니다.
    function isAccessTokenExpire() {
        let expire = false;
        if (access_token.value != &quot;&quot;) {
            // access_token에서 .(도트)로 분리하여 payload를 가져옵니다.
            let base64Payload = access_token.value.split('.')[1];

            // URL과 호환되지 않는 문자를 base64 표준 문자로 교체합니다.
            base64Payload = base64Payload.replace(/-/g, '+').replace(/_/g, '/');
            
            // atob() 메소드로 복호화합니다.
            base64Payload = atob(base64Payload);
            
            // JSON 객체로 변환합니다.
            var payloadObject = JSON.parse(base64Payload);

            // access_token의 만료 시간을 확인합니다.
            var currentDate = new Date().getTime() / 1000;
            if (payloadObject.exp &amp;lt;= currentDate) {
                console.log(&quot;token expired&quot;);
                expire = true;
            } else {
                console.log(&quot;token valid&quot;);
            }
        }
        return expire;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\stores\useLoginStore.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785915768267&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // Access Token를 갱신합니다.
    async function doRefreshToken() {
        let result = false;
        let resultErr = null;

        if (access_token.value != &quot;&quot; &amp;amp;&amp;amp; refresh_token.value != &quot;&quot;) {
            try {
                const tokenInfo = { refresh_token: refresh_token.value };
                const res = await axios.post(&quot;http://127.0.0.1:9000/api/v1/member/refresh&quot;, tokenInfo);
                if (res.data.code == 200) {
                    console.log(&quot;Access Token을 재발급(갱신)하였습니다.&quot;);
                    console.log(res.data);
                    access_token.value = res.data.access_token;
                    refresh_token.value = res.data.refresh_token;
                    result = true;
                }
            } catch(err) {
                // 400, 401, 500 에러 처리
                console.log(err);
                if (!err.response) {
                    err.response = {
                        data: {&quot;result&quot;:&quot;fail&quot;, &quot;code&quot;:401, &quot;message&quot;:&quot;Access Token을 재발급(갱신)할 수 없습니다.&quot;, access_token: &quot;&quot;, refresh_token: &quot;&quot;}
                    };
                }
            }
        } else {
            resultErr = {&quot;result&quot;:&quot;fail&quot;, &quot;code&quot;:401, &quot;message&quot;:&quot;Access Token이 없습니다.&quot;, access_token: &quot;&quot;, refresh_token: &quot;&quot;}
        }

        return new Promise((resolve, reject) =&amp;gt; {
            if (result) {
                resolve();
            } else {
                reject(resultErr);
            }
        });
    }
  
    return {
        access_token,
        refresh_token,
        isLogin,
        doLogin,
        isAccessTokenExpire,
        doRefreshToken
    }
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785915841392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import { useRouter, useRoute } from 'vue-router';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        const router = useRouter();
        const route = useRoute();
        :

        async function getLunchMenuList() {
            console.log(loginStore.access_token);
            if (loginStore.isAccessTokenExpire()) {
                console.log(&quot;Access Token이 만료되었습니다.&quot;);
                try {
                    await loginStore.doRefreshToken();
                } catch(err) {
                    console.log(err);
                    alert(&quot;인증이 만료되었습니다.\n다시 로그인 해주시기 바랍니다.&quot;);
                    router.replace({path: '/login', query: { returnUrl: route.fullPath }});
                    return;
                }
            }

            try {
                const res = await axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, {
                    headers: {
                        &quot;Authorization&quot; : &quot;Bearer &quot; + loginStore.access_token
                    }
                });
                console.log(res);
                lunchMenuList.value = res.data.data;
            } catch(err) {
                console.log(err);
            }
        }
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Access Token 만료 (10분 후) &amp;rarr;&amp;nbsp; Access&amp;nbsp;Token&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qD7OE/dJMcacKU4TW/Qn3GS0uQJdchlFhGi1SAF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qD7OE/dJMcacKU4TW/Qn3GS0uQJdchlFhGi1SAF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qD7OE/dJMcacKU4TW/Qn3GS0uQJdchlFhGi1SAF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqD7OE%2FdJMcacKU4TW%2FQn3GS0uQJdchlFhGi1SAF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; Access Token 만료 (15분 후) &amp;rarr; Refresh Token 만료 &amp;rarr; 재발급(갱신) 불가 &lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V5ynV/dJMcaazwV3r/rCuX7KDcAaqaI88QZRKukk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V5ynV/dJMcaazwV3r/rCuX7KDcAaqaI88QZRKukk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V5ynV/dJMcaazwV3r/rCuX7KDcAaqaI88QZRKukk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV5ynV%2FdJMcaazwV3r%2FrCuX7KDcAaqaI88QZRKukk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;1127&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0zA7j/dJMb998ob8n/KBtqBvDbiszSZlpqurz4T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0zA7j/dJMb998ob8n/KBtqBvDbiszSZlpqurz4T1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0zA7j/dJMb998ob8n/KBtqBvDbiszSZlpqurz4T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0zA7j%2FdJMb998ob8n%2FKBtqBvDbiszSZlpqurz4T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1127&quot; height=&quot;276&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;276&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;6. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Access Token 만료 (15분 후)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; Refresh Token 만료&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 재발급(갱신) 불가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;로그인&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/51yAz/dJMcahemhC3/bmviBcuKoPqAMIMnOz7edk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/51yAz/dJMcahemhC3/bmviBcuKoPqAMIMnOz7edk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/51yAz/dJMcahemhC3/bmviBcuKoPqAMIMnOz7edk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F51yAz%2FdJMcahemhC3%2FbmviBcuKoPqAMIMnOz7edk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>Access Token</category>
      <category>refresh token</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/339</guid>
      <comments>https://carrotweb.tistory.com/339#entry339comment</comments>
      <pubDate>Wed, 5 Aug 2026 18:12:47 +0900</pubDate>
    </item>
    <item>
      <title>[14] Backend 개발 - 사용자 API 개발, 라우트 별 인증 처리, 인가 처리</title>
      <link>https://carrotweb.tistory.com/338</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;라우트&amp;nbsp;별로&amp;nbsp;인증&amp;nbsp;처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. index.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785911757170&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

:

// Cookie 설정
app.use(cookieParser());

// 인증 설정
//app.use('/api/v1/lunchmenus', require('./src/api/v1/authMiddleware'));
// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () =&amp;gt; {
    console.log(`APIServer started on port ${port}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;lunchmenu에&amp;nbsp;대한&amp;nbsp;인증&amp;nbsp;설정을&amp;nbsp;주석&amp;nbsp;처리하거나&amp;nbsp;삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\lunchmenu.js&amp;nbsp;파일&amp;nbsp;수정&amp;nbsp;-&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;등록,&amp;nbsp;수정,&amp;nbsp;삭제일&amp;nbsp;때만&amp;nbsp;인증/인가가&amp;nbsp;처리되게&amp;nbsp;하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785911816315&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
const authMiddleware = require('./authMiddleware');

:
// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    :
});

// 점심 메뉴 번호로 검색하여 점심 메뉴를 리턴합니다.
router.get('/:no', async function(req, res, next) {
    :
});

// 점심 메뉴를 등록합니다.
router.post('/', authMiddleware, async function(req, res, next) {
    :
});

// 점심 메뉴를 수정합니다.
router.put('/:no', authMiddleware, async function(req, res, next) {
   :
});

// 점심 메뉴를 삭제합니다.
router.delete('/:no', authMiddleware, async function(req, res, next) {
    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인) &lt;br /&gt;1.&amp;nbsp;사용자&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pVnSX/dJMcaft0ENF/5CIcjDSwnieklf03p4cZYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pVnSX/dJMcaft0ENF/5CIcjDSwnieklf03p4cZYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pVnSX/dJMcaft0ENF/5CIcjDSwnieklf03p4cZYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpVnSX%2FdJMcaft0ENF%2F5CIcjDSwnieklf03p4cZYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 전체&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;등록&amp;nbsp;-&amp;nbsp;인증&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;1562&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CQ3LY/dJMcaiqIL4U/m2kyDxbR92CFcL91REshQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CQ3LY/dJMcaiqIL4U/m2kyDxbR92CFcL91REshQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CQ3LY/dJMcaiqIL4U/m2kyDxbR92CFcL91REshQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCQ3LY%2FdJMcaiqIL4U%2Fm2kyDxbR92CFcL91REshQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1562&quot; height=&quot;844&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;844&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;&lt;b&gt;인증&amp;nbsp;처리를&amp;nbsp;통한&amp;nbsp;등록&amp;nbsp;(등록자&amp;nbsp;컬럼&amp;nbsp;추가)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 수정 쿼리 실행, 데이터 업데이트 쿼리 실행&lt;/p&gt;
&lt;pre id=&quot;code_1785912056072&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use lunchdb;

-- tb_lunch_menu table에 mbr_id 컬럼 추가
ALTER TABLE tb_lunch_menu ADD mbr_id varchar(50) NOT NULL COMMENT '사용자 ID' AFTER menu_name;

-- tb_lunch_menu table에 모든 mbr_id의 값을 'system'으로 등록
UPDATE tb_lunch_menu SET mbr_id='system';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7CDN8/dJMcahFkE9m/PQQ0AP6CMFVjNq4YtsklJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7CDN8/dJMcahFkE9m/PQQ0AP6CMFVjNq4YtsklJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7CDN8/dJMcahFkE9m/PQQ0AP6CMFVjNq4YtsklJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7CDN8%2FdJMcahFkE9m%2FPQQ0AP6CMFVjNq4YtsklJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\db\mappers\mariadb\lunchmenu\lunchmenu-sql.xml 파일을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785912428683&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    :

     &amp;lt;insert id=&quot;insertLunchMenuItem&quot;&amp;gt;
        INSERT INTO tb_lunch_menu
            (
                menu_name,
                mbr_id
            )
            VALUES
            (
                #{menuName},
                #{memberID}
            )
    &amp;lt;/insert&amp;gt;

    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\lunchmenu.js 파일 수정 - 사용자 ID를 토큰에 있는 sub로 처리&lt;/p&gt;
&lt;pre id=&quot;code_1785912461233&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴를 등록합니다.
router.post('/', authMiddleware, async function(req, res, next) {
    :

    const params = {
        menuName : req.body.menuName,
        memberID : req.tokenData.sub
    };

    //const query = &quot;INSERT INTO tb_lunch_menu (menu_name, mbr_id) VALUES(?, ?)&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'insertLunchMenuItem', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.menuName, req.tokenData.sub]);
        data = await conn.query(query);
        // OkPacket { affectedRows: 1, insertId: 4n, warningStatus: 0 }
        if (data.affectedRows == 1) {
            result.message = &quot;데이터가 등록되었습니다.&quot;;
        } else {
            result.message = &quot;데이터가 등록되지 않았습니다.&quot;;
        }
    } catch (error) {
        :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9h689/dJMcad3Y8nm/pEToN7k07gJw0SHqs74fbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9h689/dJMcad3Y8nm/pEToN7k07gJw0SHqs74fbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9h689/dJMcad3Y8nm/pEToN7k07gJw0SHqs74fbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9h689%2FdJMcad3Y8nm%2FpEToN7k07gJw0SHqs74fbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AqS4f/dJMcaccY4y1/3iMagQ0ZszBn9TdWJ5MrcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AqS4f/dJMcaccY4y1/3iMagQ0ZszBn9TdWJ5MrcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AqS4f/dJMcaccY4y1/3iMagQ0ZszBn9TdWJ5MrcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAqS4f%2FdJMcaccY4y1%2F3iMagQ0ZszBn9TdWJ5MrcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;&lt;b&gt;인증&amp;nbsp;처리를&amp;nbsp;통한&amp;nbsp;수정/삭제&amp;nbsp;(등록자&amp;nbsp;비교)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\lunchmenu.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785913075049&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴를 수정합니다.
router.put('/:no', authMiddleware, async function(req, res, next) {
    :

    //const query = &quot;SELECT * FROM tb_lunch_menu WHERE seq = ?&quot;;
    const querySelect = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'selectLunchMenuItem', params, format);

    //const query = &quot;UPDATE tb_lunch_menu SET menu_name = ? WHERE seq = ?&quot;;
    const queryUpdate = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'updateLunchMenuItem', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.params.no]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            if (data[0].mbr_id == req.tokenData.sub) {
                // data = await conn.query(query, [req.body.menuName, req.params.no]);
                data = await conn.query(querySelect);
                // OkPacket { affectedRows: 1, insertId: 0n, warningStatus: 0 }
                if (data.affectedRows == 1) {
                    result.message = &quot;데이터가 수정되었습니다.&quot;;
                } else {
                    result.message = &quot;데이터가 수정되지 않았습니다.&quot;;
                }
            } else {
                result.result = &quot;fail&quot;;
                result.code = 403;
                result.message = &quot;id가 동일하지 않습니다.&quot;;
            }
        }
    }
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;수정&amp;nbsp;-&amp;nbsp;사용자&amp;nbsp;ID&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZyTT1/dJMcagGykUu/8NjQYC0TnogewFVUf4sFk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZyTT1/dJMcagGykUu/8NjQYC0TnogewFVUf4sFk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZyTT1/dJMcagGykUu/8NjQYC0TnogewFVUf4sFk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZyTT1%2FdJMcagGykUu%2F8NjQYC0TnogewFVUf4sFk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 등록된 모든 점심 메뉴의 mbr_id를 'system'으로 등록하였기 때문에 id가 일치하지 않습니다.&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6MVMk/dJMb99URhHR/wydZeA9x7MC68nIb38U4y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6MVMk/dJMb99URhHR/wydZeA9x7MC68nIb38U4y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6MVMk/dJMb99URhHR/wydZeA9x7MC68nIb38U4y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6MVMk%2FdJMb99URhHR%2FwydZeA9x7MC68nIb38U4y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\lunchmenu.js&amp;nbsp;파일&amp;nbsp;수정 &lt;/p&gt;
&lt;pre id=&quot;code_1785914088223&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴를 삭제합니다.
router.delete('/:no', authMiddleware, async function(req, res, next) {
    :

    //const query = &quot;SELECT * FROM tb_lunch_menu WHERE seq = ?&quot;;
    const querySelect = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'selectLunchMenuItem', params, format);
    
    //const query = &quot;DELETE FROM tb_lunch_menu WHERE seq = ?&quot;;
    const queryDelete = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'deleteLunchMenuItem', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.params.no]);
        data = await conn.query(querySelect);
        if (data.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            if (data[0].mbr_id == req.tokenData.sub) {
                // data = await conn.query(query, [req.params.no]);
                data = await conn.query(queryDelete);
                // OkPacket { affectedRows: 1, insertId: 0n, warningStatus: 0 }
                if (data.affectedRows == 1) {
                    result.message = &quot;데이터가 삭제되었습니다.&quot;;
                } else {
                    result.message = &quot;데이터가 삭제되지 않았습니다.&quot;;
                }
            } else {
                result.result = &quot;fail&quot;;
                result.code = 403;
                result.message = &quot;id가 동일하지 않습니다.&quot;;
            }
        }
    }
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;삭제&amp;nbsp;-&amp;nbsp;사용자&amp;nbsp;ID&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5PUna/dJMcaazwUAo/6lgEPijKAUfg8hoJKF4MxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5PUna/dJMcaazwUAo/6lgEPijKAUfg8hoJKF4MxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5PUna/dJMcaazwUAo/6lgEPijKAUfg8hoJKF4MxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5PUna%2FdJMcaazwUAo%2F6lgEPijKAUfg8hoJKF4MxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 등록된 모든 점심 메뉴의 mbr_id를 'system'으로 등록하였기 때문에 id가 일치하지 않습니다.&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crqREf/dJMcaazwUAR/V7UCroO4hC6UCcMIb7WKH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crqREf/dJMcaazwUAR/V7UCroO4hC6UCcMIb7WKH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crqREf/dJMcaazwUAR/V7UCroO4hC6UCcMIb7WKH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrqREf%2FdJMcaazwUAR%2FV7UCroO4hC6UCcMIb7WKH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;&lt;b&gt;Scope를&amp;nbsp;통한&amp;nbsp;인가(접근&amp;nbsp;권한&amp;nbsp;확인)&amp;nbsp;처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\lunchmenu.js&amp;nbsp;파일&amp;nbsp;수정 &lt;/p&gt;
&lt;pre id=&quot;code_1785914403424&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', authMiddleware, async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    const searchInfo = search(req);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: [],
        pagination: {}
    };

    // Scope로 접근 권한 확인
    const scopes = req.tokenData.scope.split(&quot; &quot;);
    if (scopes.findIndex((element) =&amp;gt; element === &quot;lunchmenu.all&quot; || element === &quot;lunchmenu.read&quot;) &amp;lt; 0) {        
        result.result = &quot;fail&quot;;
        result.code = 403;
        result.message = &quot;접근 권한이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785914450743&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴 번호로 검색하여 점심 메뉴를 리턴합니다.
router.get('/:no', authMiddleware, async function(req, res, next) {
    console.log(`REST API Get Method - Read Lunch Menu ${req.params.no}.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    };

    // Scope로 접근 권한 확인
    const scopes = req.tokenData.scope.split(&quot; &quot;);
    if (scopes.findIndex((element) =&amp;gt; element === &quot;lunchmenu.all&quot; || element === &quot;lunchmenu.read&quot;) &amp;lt; 0) {        
        result.result = &quot;fail&quot;;
        result.code = 403;
        result.message = &quot;접근 권한이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785914469298&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴를 등록합니다.
router.post('/', authMiddleware, async function(req, res, next) {
    console.log(&quot;REST API Post Method - Create Lunch Menu.&quot;);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    };

    // Scope로 접근 권한 확인
    const scopes = req.tokenData.scope.split(&quot; &quot;);
    if (scopes.findIndex((element) =&amp;gt; element === &quot;lunchmenu.all&quot; || element === &quot;lunchmenu.write&quot;) &amp;lt; 0) {        
        result.result = &quot;fail&quot;;
        result.code = 403;
        result.message = &quot;접근 권한이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785914485393&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴를 수정합니다.
router.put('/:no', authMiddleware, async function(req, res, next) {
    console.log(`REST API Put Method - Update Lunch Menu ${req.params.no}.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    }

    // Scope로 접근 권한 확인
    const scopes = req.tokenData.scope.split(&quot; &quot;);
    if (scopes.findIndex((element) =&amp;gt; element === &quot;lunchmenu.all&quot; || element === &quot;lunchmenu.write&quot;) &amp;lt; 0) {        
        result.result = &quot;fail&quot;;
        result.code = 403;
        result.message = &quot;접근 권한이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785914497641&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 점심 메뉴를 삭제합니다.
router.delete('/:no', authMiddleware, async function(req, res, next) {
    console.log(`REST API Delete Method - Delete Lunch Menu ${req.params.no}.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    }

    // Scope로 접근 권한 확인
    const scopes = req.tokenData.scope.split(&quot; &quot;);
    if (scopes.findIndex((element) =&amp;gt; element === &quot;lunchmenu.all&quot; || element === &quot;lunchmenu.delete&quot;) &amp;lt; 0) {        
        result.result = &quot;fail&quot;;
        result.code = 403;
        result.message = &quot;접근 권한이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;등록&amp;nbsp;-&amp;nbsp;사용자&amp;nbsp;Scope&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw6lw6/dJMcagzGuXu/IhJGyG6NtkbAVlsfBM0Ed1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw6lw6/dJMcagzGuXu/IhJGyG6NtkbAVlsfBM0Ed1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw6lw6/dJMcagzGuXu/IhJGyG6NtkbAVlsfBM0Ed1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw6lw6%2FdJMcagzGuXu%2FIhJGyG6NtkbAVlsfBM0Ed1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인된 사용자의 scope가 profile lunchmenu.read이기 때문에 읽기만 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;scope를 lunchmenu.all 이나 lunchmenu.write로 수정하면 등록이 됩니다.&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/338</guid>
      <comments>https://carrotweb.tistory.com/338#entry338comment</comments>
      <pubDate>Wed, 5 Aug 2026 16:27:53 +0900</pubDate>
    </item>
    <item>
      <title>[13] Backend 개발 - 사용자 API 개발, Access Token, Refresh Token</title>
      <link>https://carrotweb.tistory.com/337</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존 ID Token 생성 부분을 함수로 분리&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ID Token 생성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\member.js 파일 수정 &amp;ndash; ID Token 생성 함수로 분리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785908978678&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ID Token을 생성합니다.
async function generateIDToken(memberID, memberName, memberRoles) {
    const result = {
        result: 'success',
        message: '',
        token: ''
    };

    // 토큰 발급 시간
    const issuedAt = new Date().toLocaleString();
    const iat = Date.parse(issuedAt) / 1000;

    // ID Token - JWT
    const uuid = uuidv4().toString();
    const jwtid = uuid.replace(/-/gi, '');
    try {
        const idToken = await new Promise((resolve, reject) =&amp;gt; {
            jwt.sign({
                    name : memberName,
                    roles : memberRoles,
                    typ : &quot;ID&quot;,
                    iat : iat
                },
                secret,
                {
                    algorithm: &quot;HS256&quot;,
                    issuer : &quot;https://apiserver.example.com&quot;,
                    subject : memberID,
                    audience : &quot;lunchmenuApp&quot;,
                    expiresIn : &quot;1d&quot;,
                    jwtid : jwtid
                },
                (err, token) =&amp;gt; {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(token);
                    }
                });
        });
        result.token = idToken;
    } catch(err) {
        console.log(err);
        result.result = &quot;fail&quot;;
        result.message = &quot;ID Token 생성이 실패하였습니다.&quot;;
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type으로 추가하여 Token을 구분하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\member.js&amp;nbsp;파일&amp;nbsp;수정&amp;nbsp;&amp;ndash;&amp;nbsp;login&amp;nbsp;라우트를&amp;nbsp;수정합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785909103556&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// JWT - 암호
const secret = &quot;005c9780fe7c11eb89b4e39719de58a5&quot;;

// 사용자 ID로 검색하여 사용자를 인증하고 토큰을 리턴합니다.
router.post('/login', async function(req, res, next) {
    console.log(&quot;REST API Post Method - Member Login.&quot;);

    const result = {
        result: 'fail',
        code: 401,
        message: '',
        id_token: ''
    };

    if (req.body.id == undefined || req.body.id == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;id가 없습니다.&quot;;
        res.status(result.code).json(result);
        return;
    } else if (req.body.password == undefined || req.body.password == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;password가 없습니다.&quot;;
        res.status(result.code).json(result);
        return;
    }

    const params = {
        id : req.body.id,
        password : req.body.password
    };

    //const query = &quot;SELECT * FROM tb_member WHERE mbr_id = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
        'selectMember', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;

                // ID Token을 생성합니다.
                const resultIDToken = await generateIDToken(data[0].mbr_id, data[0].mbr_name, data[0].mbr_roles);
                if (resultIDToken.result == &quot;success&quot;) {
                    result.id_token = resultIDToken.token;
                }
            }

            if (result.id_token != &quot;&quot;) {
                result.result = &quot;success&quot;;
                result.code = 200;
            } else {
                result.code = 500;
                result.id_token = &quot;&quot;;
                result.message = &quot;토큰 생성이 실패하였습니다.&quot;;
            }
        }
    } catch (error) {
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한&amp;nbsp;암호키로&amp;nbsp;토큰을&amp;nbsp;생성하기&amp;nbsp;위해 JWT 암호를 위로&amp;nbsp;이동시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 idtoken를 id_token으로 변경하였습니다.&lt;/p&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;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인) &lt;br /&gt;사용자 로그인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLyyja/dJMcahL7i9a/wB99PiVkRA120pcvDHdKNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLyyja/dJMcahL7i9a/wB99PiVkRA120pcvDHdKNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLyyja/dJMcahL7i9a/wB99PiVkRA120pcvDHdKNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLyyja%2FdJMcahL7i9a%2FwB99PiVkRA120pcvDHdKNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;JWT&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;1359&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEYRBc/dJMcagl8bIi/fXrTufdkRyKAkOhuLB36sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEYRBc/dJMcagl8bIi/fXrTufdkRyKAkOhuLB36sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEYRBc/dJMcagl8bIi/fXrTufdkRyKAkOhuLB36sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEYRBc%2FdJMcagl8bIi%2FfXrTufdkRyKAkOhuLB36sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;875&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;875&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ID Token의 문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID&amp;nbsp;Token은&amp;nbsp;사용자를&amp;nbsp;인증하고&amp;nbsp;인증된&amp;nbsp;사용자에&amp;nbsp;대한&amp;nbsp;정보를&amp;nbsp;취득할&amp;nbsp;수&amp;nbsp;있지만&amp;nbsp;토큰의&amp;nbsp;유효&amp;nbsp;기간이&amp;nbsp;길게&amp;nbsp;설정되기&amp;nbsp;때문에&amp;nbsp;토큰이&amp;nbsp;탈취&amp;nbsp;당할&amp;nbsp;경우&amp;nbsp;보안이&amp;nbsp;취약해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi0ZPw/dJMcafU9dAO/aHwi7CSS8qkOKlI5rGImHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi0ZPw/dJMcafU9dAO/aHwi7CSS8qkOKlI5rGImHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi0ZPw/dJMcafU9dAO/aHwi7CSS8qkOKlI5rGImHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi0ZPw%2FdJMcafU9dAO%2FaHwi7CSS8qkOKlI5rGImHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;56&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;95&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;b&gt;Access Token을 사용해야 하는 이유?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰의 유효 기간을 짧게 설정하여 토큰이 탈취되어도 토큰의 유효 기간이 짧기 때문에 보안 문제를 최소화 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxBc8I/dJMcahZDeO6/9FRiPblnlHYbM1cnb1RKEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxBc8I/dJMcahZDeO6/9FRiPblnlHYbM1cnb1RKEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxBc8I/dJMcahZDeO6/9FRiPblnlHYbM1cnb1RKEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxBc8I%2FdJMcahZDeO6%2F9FRiPblnlHYbM1cnb1RKEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;115&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그러나&amp;nbsp;Access&amp;nbsp;Token의&amp;nbsp;유효&amp;nbsp;기간이&amp;nbsp;짧기&amp;nbsp;때문에&amp;nbsp;매번&amp;nbsp;사용자를&amp;nbsp;다시&amp;nbsp;인증해야&amp;nbsp;하는&amp;nbsp;문제가&amp;nbsp;발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Refresh&amp;nbsp;Token을&amp;nbsp;사용해야&amp;nbsp;하는&amp;nbsp;이유?&lt;/b&gt; &lt;br /&gt;Access&amp;nbsp;Token이&amp;nbsp;만료되었을&amp;nbsp;때&amp;nbsp;사용자를&amp;nbsp;확인하는&amp;nbsp;인증&amp;nbsp;절차를&amp;nbsp;거치지&amp;nbsp;않고&amp;nbsp;Access&amp;nbsp;Token을&amp;nbsp;재발급할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;처리해주는&amp;nbsp;토큰을&amp;nbsp;생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Io6Pc/dJMcabSE2BY/xblONqS1iuCvjH2kluu7vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Io6Pc/dJMcabSE2BY/xblONqS1iuCvjH2kluu7vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Io6Pc/dJMcabSE2BY/xblONqS1iuCvjH2kluu7vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIo6Pc%2FdJMcabSE2BY%2FxblONqS1iuCvjH2kluu7vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;127&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Access Token, Refresh Token (액세스&amp;nbsp;토큰,&amp;nbsp;리프레시&amp;nbsp;토큰)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그인하면 서버에서 사용자를 확인하여 토큰(Access Token과 Refresh Token)을 생성한 후 클라이언트에게 토큰(Access Token과 Refresh Token)을 전달하는 방식입니다. 클라이언트는 요청을 보낼 때 Authorization Header에 토큰(Access Token)을 포함하여 전달하면 서버는 전달 받은 토큰을 검증하여 인증된 사용자인지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰(Access Token, Refresh Token)은 JSON Web Token (JWT, JSON 웹 토큰)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token은 인증된 사용자에 대한 정보(claim(클레임))과 서버 리소스에 접근할 수 있는 권한(scope(스코프))이 포함되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Refresh Token은 Access Token을 재발급하는데 사용되는 토큰입니다.&lt;/p&gt;
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트에서 토큰을 관리하기 때문에 서버에 부하가 낮아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰이 탈취 당할 수 있습니다. &amp;rarr; 보안성이 취약함&lt;/li&gt;
&lt;li&gt;(HTTPS로 보안할 수 있습니다. 단, 클라이언트 해킹은 차단할 수 없습니다.)&lt;/li&gt;
&lt;li&gt;모든 자원을 접근할 수 있습니다. &amp;rarr; 별도의 접근 권한 처리 필요 &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token, Refresh Token Process (Access / Refresh 토큰 생성 프로세스)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKVgQW/dJMcafnkjVU/vakLNKNqzCnxxNOixr1Kx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKVgQW/dJMcafnkjVU/vakLNKNqzCnxxNOixr1Kx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKVgQW/dJMcafnkjVU/vakLNKNqzCnxxNOixr1Kx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKVgQW%2FdJMcafnkjVU%2FvakLNKNqzCnxxNOixr1Kx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1459&quot; height=&quot;800&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;800&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;Access Token, Refresh Token Process (Access Token 만료 시 프로세스)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lu688/dJMcacjIOlY/XLVlRJoD6pg055KXhJDMJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lu688/dJMcacjIOlY/XLVlRJoD6pg055KXhJDMJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lu688/dJMcacjIOlY/XLVlRJoD6pg055KXhJDMJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLu688%2FdJMcacjIOlY%2FXLVlRJoD6pg055KXhJDMJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1459&quot; height=&quot;753&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;753&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;&lt;b&gt;Access / &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Refresh&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Token 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\member.js 파일 수정 - Access Token 생성 함수를 추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785910107142&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Access Token을 생성합니다.
async function generateAccessToken(memberID, memberName, memberRoles) {
    const result = {
        result: 'success',
        message: '',
        token: ''
    };

    // 토큰 발급 시간
    const issuedAt = new Date().toLocaleString();
    const iat = Date.parse(issuedAt) / 1000;

    // Access Token - JWT
    const uuid = uuidv4().toString();
    const jwtid = uuid.replace(/-/gi, '');
    try {
        const accessToken = await new Promise((resolve, reject) =&amp;gt; {
            jwt.sign({
                    name : memberName,
                    roles : memberRoles,
                    typ : &quot;Bearer&quot;,
                    scope: &quot;profile lunchmenu.all&quot;,
                    iat : iat
                },
                secret,
                {
                    algorithm: &quot;HS256&quot;,
                    issuer : &quot;apiserver.example.com&quot;,
                    subject : memberID,
                    audience : &quot;lunchmenuApp&quot;,
                    expiresIn : &quot;10m&quot;,
                    jwtid : jwtid
                },
                (err, token) =&amp;gt; {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(token);
                    }
                });
        });
        result.token = accessToken;
    } catch(err) {
        console.log(err);
        result.result = &quot;fail&quot;;
        result.message = &quot;Refresh Token 생성이 실패하였습니다.&quot;;
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type으로 Token 구분 : Bearer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scope로 서버 리소스에 접근할 수 있는 권한 설정 : profile&amp;nbsp;lunchmenu.all&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\member.js 파일 수정 - Refresh Token 생성 함수를 추가합니다.&amp;nbsp; &lt;/p&gt;
&lt;pre id=&quot;code_1785910189207&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Refresh Token을 생성합니다.
async function generateRefreshToken(memberID) {
    const result = {
        result: 'success',
        message: '',
        token: ''
    };

    // 토큰 발급 시간
    const issuedAt = new Date().toLocaleString();
    const iat = Date.parse(issuedAt) / 1000;

    // Refresh Token - JWT
    const uuid = uuidv4().toString();
    const jwtid = uuid.replace(/-/gi, '');
    try {
        const refreshToken = await new Promise((resolve, reject) =&amp;gt; {
            jwt.sign({
                    typ : &quot;Refresh&quot;,
                    scope: &quot;profile scopes&quot;,
                    iat : iat
                },
                secret,
                {
                    algorithm: &quot;HS256&quot;,
                    issuer : &quot;apiserver.example.com&quot;,
                    subject : memberID,
                    audience : &quot;lunchmenuApp&quot;,
                    expiresIn : &quot;15m&quot;,
                    jwtid : jwtid
                },
                (err, token) =&amp;gt; {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(token);
                    }
                });
        });
        result.token = refreshToken;
    } catch(err) {
        console.log(err);
        result.result = &quot;fail&quot;;
        result.message = &quot;Refresh Token 생성이 실패하였습니다.&quot;;
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type으로 Token 구분 : Refresh&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scope로 서버 리소스에 접근할 수 있는 권한 설정 : profile&amp;nbsp;scopes&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\api\v1\member.js 파일 수정 - login 라우트를 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785910458424&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
// 사용자 ID로 검색하여 사용자를 인증하고 토큰을 리턴합니다.
router.post('/login', async function(req, res, next) {
    console.log(&quot;REST API Post Method - Member Login.&quot;);

    const result = {
        result: 'fail',
        code: 401,
        message: '',
        access_token: '',
        refresh_token: ''
    };

    if (req.body.id == undefined || req.body.id == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;id가 없습니다.&quot;;
        res.status(result.code).json(result);
        return;
    } else if (req.body.password == undefined || req.body.password == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;password가 없습니다.&quot;;
        res.status(result.code).json(result);
        return;
    }

    const params = {
        id : req.body.id,
        password : req.body.password
    };

    //const query = &quot;SELECT * FROM tb_member WHERE mbr_id = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
        'selectMember', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;

                // Access Token을 생성합니다.
                const resultAccessToken = await generateAccessToken(data[0].mbr_id, data[0].mbr_name, data[0].mbr_roles);
                if (resultAccessToken.result == &quot;success&quot;) {
                    result.access_token = resultAccessToken.token;
                }

                // Refresh Token을 생성합니다.
                const resultRefreshToken = await generateRefreshToken(data[0].mbr_id);
                if (resultRefreshToken.result == &quot;success&quot;) {
                    result.refresh_token = resultRefreshToken.token;
                }
            }

            if (result.access_token != &quot;&quot; &amp;amp;&amp;amp; result.refresh_token != &quot;&quot;) {
                result.result = &quot;success&quot;;
                result.code = 200;
            } else {
                result.code = 500;
                result.access_token = &quot;&quot;;
                result.refresh_token = &quot;&quot;;
                result.message = &quot;토큰 생성이 실패하였습니다.&quot;;
            }
        }
    } catch (error) {
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id_token를 지우고 access_token, refresh_token를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 ID Token 생성을 주석 처리하거나 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\api\v1\authMiddleware.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785910596696&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Token 방식 (Access Token, Refresh Token)
const jwt = require('jsonwebtoken');

const authMiddleware = async (req, res, next) =&amp;gt; {
    let authorization = req.header('Authorization');

    const result = {
        result: 'fail',
        code: 401,
        message: '인증이 실패하였습니다.',
        data: {}
    };

    if (authorization == undefined || authorization == &quot;&quot;) {
        result.code = 400;
        res.status(result.code).json(result);
    } else {
        authorization = authorization.replace(&quot;Bearer &quot;,&quot;&quot;);
        try {
            const secret = &quot;005c9780fe7c11eb89b4e39719de58a5&quot;;
            const tokenData = await new Promise((resolve, reject) =&amp;gt; {
                jwt.verify(authorization, secret, 
                    (err, decoded) =&amp;gt; {
                        if (err) {
                            reject(err);
                        } else {
                            resolve(decoded);
                        }
                    });
            });
            if (tokenData.typ == &quot;Bearer&quot;) {
                req.tokenData = tokenData;
                next();
            }
        } catch(err) {
            if (err instanceof jwt.TokenExpiredError) {
                // 토큰 만료 - 401(Unauthorized), 498(Invalid token)
                result.message = &quot;토큰이 만료되었습니다.&quot;;
            } else if (err instanceof jwt.JsonWebTokenError) {
                // 토큰 에러 - Invalid token
                result.message = &quot;토큰이 유효하지 않습니다.&quot;;
            } else {
                result.message = &quot;토큰이 유효하지 않습니다.&quot;;
            }
            res.status(result.code).json(result);
        }
    }
}

module.exports = authMiddleware;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Type이 &lt;/span&gt;Bearer일 때만 next()를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 API 테스트 (POSTMAN에서 확인)&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlt1we/dJMcagsVwyA/8CTnn9t4lI63S8uYJ9vY9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlt1we/dJMcagsVwyA/8CTnn9t4lI63S8uYJ9vY9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlt1we/dJMcagsVwyA/8CTnn9t4lI63S8uYJ9vY9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdlt1we%2FdJMcagsVwyA%2F8CTnn9t4lI63S8uYJ9vY9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;Access Token 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvGsb0/dJMcadv5KXe/hWPz5kAMHKxi1GIF15uY2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvGsb0/dJMcadv5KXe/hWPz5kAMHKxi1GIF15uY2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvGsb0/dJMcadv5KXe/hWPz5kAMHKxi1GIF15uY2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvGsb0%2FdJMcadv5KXe%2FhWPz5kAMHKxi1GIF15uY2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;875&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;875&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;Refresh&amp;nbsp;Token&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;1359&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YaPPo/dJMcabdZRqa/DMsW8DvIKT66rYSbSMt2Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YaPPo/dJMcabdZRqa/DMsW8DvIKT66rYSbSMt2Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YaPPo/dJMcabdZRqa/DMsW8DvIKT66rYSbSMt2Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYaPPo%2FdJMcabdZRqa%2FDMsW8DvIKT66rYSbSMt2Tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;875&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;875&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;&lt;b&gt;Refresh&amp;nbsp;Token&amp;nbsp;Rotation&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token과 Refresh Token 둘 다 탈취가 될 경우 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&amp;nbsp;Access&amp;nbsp;Token이&amp;nbsp;재발급될&amp;nbsp;때&amp;nbsp;Refresh&amp;nbsp;Token도&amp;nbsp;같이&amp;nbsp;재발급되게&amp;nbsp;하여&amp;nbsp;보안&amp;nbsp;문제를&amp;nbsp;최소화&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpfgZp/dJMcaaGkLaC/63UnaAc0GXEjGkFK1xMzS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpfgZp/dJMcaaGkLaC/63UnaAc0GXEjGkFK1xMzS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpfgZp/dJMcaaGkLaC/63UnaAc0GXEjGkFK1xMzS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpfgZp%2FdJMcaaGkLaC%2F63UnaAc0GXEjGkFK1xMzS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;194&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\member.js&amp;nbsp;파일&amp;nbsp;수정&amp;nbsp;-&amp;nbsp;refresh&amp;nbsp;라우트를&amp;nbsp;추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785911294955&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Refresh Token으로 Access Token를 재발급(갱신)합니다.
// Refresh Token도 재발급합니다.
router.post('/refresh', async function(req, res, next) {
    console.log(&quot;REST API Post Method - Token Refresh.&quot;);

    const result = {
        result: 'fail',
        code: 401,
        message: '',
        access_token: '',
        refresh_token: ''
    };

    if (req.body.refresh_token == undefined || req.body.refresh_token == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;Refresh Token이 없습니다.&quot;;
        res.status(result.code).json(result);
        return;
    }

    const refreshToken = req.body.refresh_token;

    // 토큰 검증
    let tokenData = {};
    try {
        tokenData = await new Promise((resolve, reject) =&amp;gt; {
            jwt.verify(refreshToken, secret, 
                (err, decoded) =&amp;gt; {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(decoded);
                    }
                });
        });
    } catch(err) {
        if (err instanceof jwt.TokenExpiredError) {
            // 토큰 만료 - 401(Unauthorized), 498(Invalid token)
            result.message = &quot;Refresh Token이 만료되어 Access Token을 재발급(갱신)할 수 없습니다.&quot;;
        } else if (err instanceof jwt.JsonWebTokenError) {
            // 토큰 에러 - Invalid token
            result.message = &quot;Refresh Token이 유효하지 않습니다.&quot;;
        } else {
            result.message = &quot;Refresh Token이 유효하지 않습니다.&quot;;
        }
        res.status(result.code).json(result);
        return;
    }

    const params = {
        id : tokenData.sub
    };

    //const query = &quot;SELECT * FROM tb_member WHERE mbr_id = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
        'selectMember', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [tokenData.sub]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.message = &quot;사용자가 없어 Access Token을 재발급(갱신)할 수 없습니다.&quot;;
        } else {
            // Access Token을 생성합니다.
            const resultAccessToken = await generateAccessToken(data[0].mbr_id, data[0].mbr_name, data[0].mbr_roles);
            if (resultAccessToken.result == &quot;success&quot;) {
                result.access_token = resultAccessToken.token;
            }

            // Refresh Token을 생성합니다.
            const resultRefreshToken = await generateRefreshToken(data[0].mbr_id);
            if (resultRefreshToken.result == &quot;success&quot;) {
                result.refresh_token = resultRefreshToken.token;
            }

            if (result.access_token != &quot;&quot; &amp;amp;&amp;amp; result.refresh_token != &quot;&quot;) {
                result.result = &quot;success&quot;;
                result.code = 200;
                result.message = &quot;Access Token을 재발급(갱신)하였습니다.&quot;;
            } else {
                result.code = 500;
                result.access_token = &quot;&quot;;
                result.refresh_token = &quot;&quot;;
                result.message = &quot;Access Token을 재발급(갱신)할 수 없습니다.&quot;;
            }
        }
    } catch (error) {
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없어 Access Token을 재발급(갱신)할 수 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하여 Access Token을 재발급(갱신)할 수 없습니다.&quot;;
        }
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 API 테스트 (POSTMAN에서 확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;조회&amp;nbsp;-&amp;nbsp;Access&amp;nbsp;Token&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDfSde/dJMcaidi0Bv/oovtKM5MiD6nKj8SRQpckK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDfSde/dJMcaidi0Bv/oovtKM5MiD6nKj8SRQpckK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDfSde/dJMcaidi0Bv/oovtKM5MiD6nKj8SRQpckK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDfSde%2FdJMcaidi0Bv%2FoovtKM5MiD6nKj8SRQpckK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;Access&amp;nbsp;Token&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XbGqe/dJMcabkVQE4/Xv4rTjFk5CZWEpkPq8Klr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XbGqe/dJMcabkVQE4/Xv4rTjFk5CZWEpkPq8Klr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XbGqe/dJMcabkVQE4/Xv4rTjFk5CZWEpkPq8Klr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXbGqe%2FdJMcabkVQE4%2FXv4rTjFk5CZWEpkPq8Klr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;Access Token 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HepLY/dJMcahL7kRc/90ptdfqQGjjlYotSflu0L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HepLY/dJMcahL7kRc/90ptdfqQGjjlYotSflu0L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HepLY/dJMcahL7kRc/90ptdfqQGjjlYotSflu0L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHepLY%2FdJMcahL7kRc%2F90ptdfqQGjjlYotSflu0L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;875&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;875&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;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>Access Token</category>
      <category>refresh token</category>
      <category>Refresh Token Rotation</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/337</guid>
      <comments>https://carrotweb.tistory.com/337#entry337comment</comments>
      <pubDate>Wed, 5 Aug 2026 15:36:04 +0900</pubDate>
    </item>
    <item>
      <title>[12] Frontend 개발 - 상태 관리, Login 관리, ID Token</title>
      <link>https://carrotweb.tistory.com/336</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;로그인뷰 생성하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src에&amp;nbsp;Account&amp;nbsp;디렉터리를&amp;nbsp;생성하고&amp;nbsp;LoginView.vue&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dy7Dhp/dJMcahZCEtz/2PY8U7PYG2GMh2F9hiKFtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dy7Dhp/dJMcahZCEtz/2PY8U7PYG2GMh2F9hiKFtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dy7Dhp/dJMcahZCEtz/2PY8U7PYG2GMh2F9hiKFtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdy7Dhp%2FdJMcahZCEtz%2F2PY8U7PYG2GMh2F9hiKFtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785831737755&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;login&quot;&amp;gt;
        &amp;lt;form class=&quot;loginform&quot;&amp;gt;
            &amp;lt;p&amp;gt;
                &amp;lt;label for=&quot;memberIdInput&quot;&amp;gt;아이디&amp;lt;/label&amp;gt;
                &amp;lt;input type=&quot;text&quot; id=&quot;memberIdInput&quot; class=&quot;input_text&quot; ref=&quot;memberIdInput&quot; v-model.trim=&quot;memberId&quot; placeholder=&quot;아이디를 입력하세요.&quot; /&amp;gt;
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                &amp;lt;label for=&quot;memberPasswordInput&quot;&amp;gt;패스워드&amp;lt;/label&amp;gt;
                &amp;lt;input type=&quot;password&quot; id=&quot;memberPasswordInput&quot; class=&quot;input_text&quot; ref=&quot;memberPasswordInput&quot; v-model.trim=&quot;memberPassword&quot; placeholder=&quot;패스워드를 입력하세요.&quot; /&amp;gt;
            &amp;lt;/p&amp;gt;
            &amp;lt;p class=&quot;buttons&quot;&amp;gt;
                &amp;lt;button @click.prevent=&quot;loginClick&quot; class=&quot;button blue&quot;&amp;gt;로그인&amp;lt;/button&amp;gt;
                &amp;lt;button @click.prevent=&quot;cancelClick&quot; class=&quot;button&quot;&amp;gt;취소&amp;lt;/button&amp;gt;
            &amp;lt;/p&amp;gt;
        &amp;lt;/form&amp;gt;
        &amp;lt;p&amp;gt;{{ errorMessage }}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { reactive, toRefs, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';

export default {
    name : 'LoginView',
    setup() {
        const router = useRouter();

        const state = reactive({
            memberId: &quot;&quot;,
            memberPassword: &quot;&quot;,
            errorMessage: &quot;&quot;
        });

        const memberIdInput = ref(null);
        const memberPasswordInput = ref(null);

        function loginClick() {
            if (state.memberId == &quot;&quot;) {
                alert(&quot;아이디를 입력하세요.&quot;);
                memberIdInput.value.focus();
                return;
            } else if (state.memberPassword == &quot;&quot;) {
                alert(&quot;패스워드를 입력하세요.&quot;);
                memberPasswordInput.value.focus();
                return;
            }
        }
        function cancelClick() {
            router.back();
        }

        onMounted(()=&amp;gt;{
            memberIdInput.value.focus();
        });

        return {
            ...toRefs(state),
            loginClick,
            cancelClick,
            memberIdInput,
            memberPasswordInput
        }
    }
};
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.login { width:800px; margin:20px auto; }
.loginform { width:400px; margin:auto; }
.loginform p &amp;gt; label { display:inline-block; width:100px; font-size:14px; padding-right:10px; }
.loginform p &amp;gt; .input_text { width:200px; font-size:14px; height:32px; }
.buttons { position:relative; height:32px; margin-top:20px; }
.buttons &amp;gt; .button { overflow:visible; cursor:pointer; min-width:125px; height:32px; margin:0 2px; padding:0 15px; line-height:32px; font-size:14px; border:1px solid #dfdfdf; background:#fff; border-radius:10px; }
.buttons &amp;gt; .button.blue { color:#fff; border-color:#0099d2 !important; background:#0099d2 !important; }
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\router\index.js에서&amp;nbsp;로그인을&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785831852431&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import LoginView from '@/views/Account/LoginView.vue'

:
const routes = [
  :
  {
    path: '/login',
    name: 'Login',
    component: LoginView
  }
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 웹 브라우저에서 확인하기 (http://localhost:8080/login)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8u5PE/dJMcaafdlB6/Kiqj5WpHAChpAnX2mwl20K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8u5PE/dJMcaafdlB6/Kiqj5WpHAChpAnX2mwl20K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8u5PE/dJMcaafdlB6/Kiqj5WpHAChpAnX2mwl20K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8u5PE%2FdJMcaafdlB6%2FKiqj5WpHAChpAnX2mwl20K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&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;&lt;b&gt;Login&amp;nbsp;Store(로그인&amp;nbsp;스토어)&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores에 useLoginStore.js 파일을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFFiAc/dJMb99N1svq/OZhwbxiNkqKCFiIA095fbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFFiAc/dJMb99N1svq/OZhwbxiNkqKCFiIA095fbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFFiAc/dJMb99N1svq/OZhwbxiNkqKCFiIA095fbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFFiAc%2FdJMb99N1svq%2FOZhwbxiNkqKCFiIA095fbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785832165615&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () =&amp;gt; {
    const idToken = ref(&quot;&quot;);

    // 로그인 여부를 가져옵니다.
    const isLogin = computed(() =&amp;gt; {
        return idToken.value == &quot;&quot; ? false : true;
    });

    // 로그인합니다.
    async function doLogin(memberInfo) {
        let result = false;
        let resultErr = null;

        try {
            const res = await axios.post(&quot;http://127.0.0.1:9000/api/v1/member/login&quot;, memberInfo);
            if (res.data.code == 200) {
                console.log(&quot;로그인되었습니다.&quot;);
                idToken.value = res.data.idtoken;
                result = true;
            }
        } catch(err) {
            // 400, 401, 500 에러 처리
            console.log(err);
            if (!err.response) {
                err.response = {
                    data:{&quot;result&quot;:&quot;fail&quot;, &quot;code&quot;:401, &quot;message&quot;:&quot;사용자가 없습니다.&quot;, idtoken: &quot;&quot;}
                };
            }
            resultErr = err.response.data;
        }

        return new Promise((resolve, reject) =&amp;gt; {
            if (result) {
                resolve();
            } else {
                reject(resultErr);
            }
        });
    }
  
    return {
        idToken,
        isLogin,
        doLogin
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\Account\LoginView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785832293727&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import { useLoginStore } from '@/stores/useLoginStore';

export default {
    name : 'LoginView',
    setup() {
        :

        const loginStore = useLoginStore();

        function loginClick() {
            :

            const memberInfo = { id: state.memberId, password: state.memberPassword };
            loginStore.doLogin(memberInfo).then(() =&amp;gt; {
                router.replace({name : 'home'});
            }).catch((err) =&amp;gt; {
                state.errorMessage = err.message;
            });
        }
        :

    }
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 웹 브라우저에서 확인하기 (http://localhost:8080/login) - 로그인 실패 테스트&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KePo0/dJMcag0GxDl/9j49Cd45iasrku6DvJ0sak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KePo0/dJMcag0GxDl/9j49Cd45iasrku6DvJ0sak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KePo0/dJMcag0GxDl/9j49Cd45iasrku6DvJ0sak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKePo0%2FdJMcag0GxDl%2F9j49Cd45iasrku6DvJ0sak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&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;Navigation&amp;nbsp;Guards&amp;nbsp;(내비게이션&amp;nbsp;가드) &lt;br /&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/advanced/navigation-guards.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/advanced/navigation-guards.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0OPd2/dJMcaidihAq/tuDhskpD1P61LkXQsIRDb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0OPd2/dJMcaidihAq/tuDhskpD1P61LkXQsIRDb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0OPd2/dJMcaidihAq/tuDhskpD1P61LkXQsIRDb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0OPd2%2FdJMcaidihAq%2FtuDhskpD1P61LkXQsIRDb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;845&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&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;b&gt;Navigation&amp;nbsp;Guards&amp;nbsp;(내비게이션&amp;nbsp;가드)로&amp;nbsp;처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\router\index.js에서 로그인을 위한 내비게이션 가드를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[라우트 전역 처리]&lt;/p&gt;
&lt;pre id=&quot;code_1785832477510&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useLoginStore } from '@/stores/useLoginStore'

:

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

router.beforeEach((to, from, next) =&amp;gt; {
  const loginStore = useLoginStore();
  if (to.name.startsWith(&quot;lunchmenu&quot;)) {
    if (!loginStore.isLogin) {
      next({path: '/login', query: { returnUrl: to.fullPath }});
      //next('/login?returnUrl=' + to.fullPath);
    } else {
      next();
    }
  } else {
    next();
  }
});

export default router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export 전에 router.beforeEach를 추가합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;to는 이동할 URL에 대한 객체&lt;/li&gt;
&lt;li&gt;form은 현재 URL에 대한 객체&lt;/li&gt;
&lt;li&gt;next는 이동하기 위한 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 &quot;lunchmenu&quot;로 시작하고 로그인이 되지 않은 상태일 때만 로그인이 적용되게 하합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;returnUrl로&amp;nbsp;이동할&amp;nbsp;URL의&amp;nbsp;경로를&amp;nbsp;전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[라우트 별 처리]&lt;/p&gt;
&lt;pre id=&quot;code_1785832570931&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useLoginStore } from '@/stores/useLoginStore'

:
const routes = [
  :
  {
    path: '/lunchmenu',
    component: LunchMenuView,
    beforeEnter : (to, from, next) =&amp;gt; {
      const loginStore = useLoginStore();
      if (to.name.startsWith(&quot;lunchmenu&quot;)) {
        if (!loginStore.isLogin) {
          next({path: '/login', query: { returnUrl: to.fullPath }});
        } else {
          next();
        }
      } else {
        next();
      }
    },
    children : [
      :
    ]
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[메서드를 생성하고 라우트 별 처리]&lt;/p&gt;
&lt;pre id=&quot;code_1785832620605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useLoginStore } from '@/stores/useLoginStore'

:
const Authentication = () =&amp;gt; (to, from, next) =&amp;gt; {
  const loginStore = useLoginStore();
  if (to.name.startsWith(&quot;lunchmenu&quot;)) {
    if (!loginStore.isLogin) {
      next({path: '/login', query: { returnUrl: to.fullPath }});
    } else {
      next();
    }
  } else {
    next();
  }
};

const routes = [
  :
  {
    path: '/lunchmenu',
    component: LunchMenuView,
    beforeEnter: Authentication(),
    children : [
      :
    ]
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\Account\LoginView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785832673173&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import { useRouter, useRoute } from 'vue-router';
:

export default {
    name : 'LoginView',
    setup() {
        const router = useRouter();
        const route = useRoute();

        function loginClick() {
            :

            const memberInfo = { id: state.memberId, password: state.memberPassword };
            loginStore.doLogin(memberInfo).then(() =&amp;gt; {
                //router.replace({name : 'home'});
                const returnUrl = route.query.returnUrl;
                router.replace({path : returnUrl});
            }).catch((err) =&amp;gt; {
                state.errorMessage = err.message;
            });
        }
        :
    }
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list &amp;rarr; http://localhost:8080/login?returnUrl=/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfRi7t/dJMcaiKYLOv/WY9weJtrmYtNCpkHZZ1FQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfRi7t/dJMcaiKYLOv/WY9weJtrmYtNCpkHZZ1FQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfRi7t/dJMcaiKYLOv/WY9weJtrmYtNCpkHZZ1FQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfRi7t%2FdJMcaiKYLOv%2FWY9weJtrmYtNCpkHZZ1FQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\Account\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785832814549&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import { useLoginStore } from '@/stores/useLoginStore';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        :
        const loginStore = useLoginStore();

        function getLunchMenuList() {
            console.log(loginStore.idToken);
            axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, {
                headers: {
                    &quot;Authorization&quot; : &quot;Bearer &quot; + loginStore.idToken
                }
            }).then((res) =&amp;gt; {
                console.log(res);
                lunchMenuList.value = res.data.data;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }
        :
    }
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q5JyF/dJMcagNjnxC/Eg68xe47bObPRTotMkp4q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q5JyF/dJMcagNjnxC/Eg68xe47bObPRTotMkp4q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q5JyF/dJMcagNjnxC/Eg68xe47bObPRTotMkp4q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq5JyF%2FdJMcagNjnxC%2FEg68xe47bObPRTotMkp4q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>defineStore</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/336</guid>
      <comments>https://carrotweb.tistory.com/336#entry336comment</comments>
      <pubDate>Tue, 4 Aug 2026 17:44:45 +0900</pubDate>
    </item>
    <item>
      <title>[11] Frontend 개발 - 상태 관리, Store &amp;ndash; Pinia</title>
      <link>https://carrotweb.tistory.com/335</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Store를 사용해야 하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store는 애플리케이션 전반에서 접근할 수 있는 데이터를 포함해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 컴포넌트에서 사용되는 데이터(사용자 정보, 네비게이션 정보, 전역 정보)와 컴포넌트에서 보존되어야 하는 데이터(Form 데이터)가 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store는 state와 비즈니스 로직을 보유하는 독립체(entity)로 컴포넌트 트리에 묶여 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 전역 state로 모든 컴포넌트가 읽고 쓸 수 있습니다. &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;1092&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1t5DX/dJMcacYmuPt/kv9VvNO9yGQeUGut3dnqS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1t5DX/dJMcacYmuPt/kv9VvNO9yGQeUGut3dnqS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1t5DX/dJMcacYmuPt/kv9VvNO9yGQeUGut3dnqS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1t5DX%2FdJMcacYmuPt%2Fkv9VvNO9yGQeUGut3dnqS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1092&quot; height=&quot;325&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;325&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;b&gt;Computed (계산된 속성) 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 데이터를 계산하는 코드가 &amp;lt;template&amp;gt; 내에 있거나 반복되면 &amp;lt;template&amp;gt;이 복잡해 보일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 계산하는 코드를 분리하고 반응되도록&amp;nbsp;&amp;nbsp;computered() 함수로 처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785830655250&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;리스트가 {{ lunchMenuList.value.length &amp;gt; 0 ? &quot;있습니다&quot; : &quot;없습니다&quot; }} &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
    import { ref } from 'vue'
    const lunchMenuList = ref([]);

    return {
        lunchMenuList
    }
&amp;lt;/setup&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785830677674&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;리스트가 {{ isLunchMenu }} &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
    import { ref, computed } from 'vue'
    const lunchMenuList = ref([]);

    // 계산된 ref
    const isLunchMenu = computed(() =&amp;gt; {
        return lunchMenuList.value.length &amp;gt; 0 ? &quot;있습니다&quot; : &quot;없습니다&quot;;
    });

    return {
        lunchMenuList,
        isLunchMenu
    }
&amp;lt;/setup&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\views\LunchMenu\LunchMenuListView.vue에&amp;nbsp;computed을&amp;nbsp;추가하고&amp;nbsp;사용합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785830756005&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;{{ appTitle }} - {{ lunchmenuTitle }} 리스트가 {{  isLunchMenu }}
        &amp;lt;button @click=&quot;countStore.incrementByRandom()&quot;&amp;gt;{{ countStore.count }}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    :
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref, computed } from 'vue';
:

export default {
    setup() {
        :
        // 계산된 ref
        const isLunchMenu = computed(() =&amp;gt; {
            return lunchMenuList.value.length &amp;gt; 0 ? &quot;있습니다&quot; : &quot;없습니다&quot;;
        });

        return {
            :
            isLunchMenu
        }
    },
    :
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/list) &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fu66F/dJMcahyBV5v/CwHdoE23Xz0cgyRxGNHBR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fu66F/dJMcahyBV5v/CwHdoE23Xz0cgyRxGNHBR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fu66F/dJMcahyBV5v/CwHdoE23Xz0cgyRxGNHBR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFu66F%2FdJMcahyBV5v%2FCwHdoE23Xz0cgyRxGNHBR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&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;&lt;b&gt;상태 관리 - pinia&lt;/b&gt;&lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/scaling-up/state-management.html#pinia&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/scaling-up/state-management.html#pinia&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blPuvr/dJMcacKUkcV/ZrIgTpk2eZUK1i4kmYg8dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blPuvr/dJMcacKUkcV/ZrIgTpk2eZUK1i4kmYg8dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blPuvr/dJMcacKUkcV/ZrIgTpk2eZUK1i4kmYg8dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblPuvr%2FdJMcacKUkcV%2FZrIgTpk2eZUK1i4kmYg8dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;845&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&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;Pinia(피니아) &lt;br /&gt;(&lt;a href=&quot;https://pinia.vuejs.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pinia.vuejs.kr/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3qSsA/dJMcafANuFO/vlAHd8Ul0ckXz6PfMG3HQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3qSsA/dJMcafANuFO/vlAHd8Ul0ckXz6PfMG3HQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3qSsA/dJMcafANuFO/vlAHd8Ul0ckXz6PfMG3HQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3qSsA%2FdJMcafANuFO%2FvlAHd8Ul0ckXz6PfMG3HQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;845&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 상태 관리를 사용하기 위해 Pinia 설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785830900171&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install pinia --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRva79/dJMcac46Oi4/6vXuKPpZokjWKNFgNySuI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRva79/dJMcac46Oi4/6vXuKPpZokjWKNFgNySuI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRva79/dJMcac46Oi4/6vXuKPpZokjWKNFgNySuI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRva79%2FdJMcac46Oi4%2F6vXuKPpZokjWKNFgNySuI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;863&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\main.js에&amp;nbsp;pinia&amp;nbsp;인스턴스(루트&amp;nbsp;Store)를&amp;nbsp;추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785830973515&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue';
import { ref } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';

const pinia = createPinia();
const app = createApp(App);

app.use(pinia);

app.provide('appTitle', '점심 메뉴 앱');

const greetingMessage = ref('즐거운 점심 시간되세요!');
const likeCount = ref(0);

function likeUp() {
    this.likeCount++;
}

app.provide('useCommonStore', {
    greetingMessage,
    likeCount,
    likeUp
});

app.use(router).mount('#app');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Store(스토어)&amp;nbsp;정의&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store는&amp;nbsp;defineStore()를&amp;nbsp;사용하여&amp;nbsp;정의합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785831043064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';

export const useMyStore = defineStore('my', {
    state: () =&amp;gt; ({
        :
    }),
    getters: {
        :
    },
    actions: {
        :
    },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store에서는 data는 state로, computed는 getters로, methods는 actions로 생각하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;use&quot;로 시작해 &quot;Store&quot;로 끝나는 Store의 이름을 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state에 정의된 프로퍼티만 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getters, actions은 this를 통해 state에 정의된 프로퍼티에 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;actions은&amp;nbsp;비동기라&amp;nbsp;API를&amp;nbsp;호출할&amp;nbsp;경우&amp;nbsp;async&amp;nbsp;await로&amp;nbsp;처리해야&amp;nbsp;합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\stores에&amp;nbsp;useLikeStore.js&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNPc3f/dJMcajiTXv9/YaaPENECJlcvlBfXrGSWgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNPc3f/dJMcajiTXv9/YaaPENECJlcvlBfXrGSWgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNPc3f/dJMcajiTXv9/YaaPENECJlcvlBfXrGSWgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNPc3f%2FdJMcajiTXv9%2FYaaPENECJlcvlBfXrGSWgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&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;Options API로 개발하면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785831224935&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';

export const useLikeStore = defineStore('like', {
    state: () =&amp;gt; {
        return {
            likeCount: 0
        }
    },
    getters: {
        getLikeMessage: ((state) =&amp;gt; {
            return &quot;좋아요가 &quot; + state.likeCount + &quot;개 있습니다.&quot;;
        })
    },
    actions: {
        // Like를 증가시킵니다.
        increment() {
            this.likeCount++
        }
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Options API 대신 Composition API로 개발하시기 바랍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785831181463&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useLikeStore = defineStore('like', () =&amp;gt; {
    const likeCount = ref(0);

    const getLikeMessage = computed(() =&amp;gt; {
        return &quot;좋아요가 &quot; + likeCount.value + &quot;개 있습니다.&quot;;
    });

    // Like를 증가시킵니다.
    function increment() {
        likeCount.value++;
    }
  
    return {
        likeCount,
        getLikeMessage,
        increment
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref()는&amp;nbsp;state&amp;nbsp;프로퍼티,&amp;nbsp;computed()는&amp;nbsp;getters&amp;nbsp;프로퍼티,&amp;nbsp;function()은&amp;nbsp;actions&amp;nbsp;프로퍼티가&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuListView.vue에&amp;nbsp;useLikeStore를&amp;nbsp;추가하고&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785831437344&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    :
    &amp;lt;div&amp;gt;
        &amp;lt;span&amp;gt;{{ greetingMessage }}&amp;lt;/span&amp;gt;
        &amp;lt;button @click=&quot;likeStore.increment()&quot;&amp;gt;{{ likeStore.getLikeMessage }}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    :
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { useLikeStore } from '@/stores/useLikeStore';
:

export default {
    setup() {
        :
        const likeStore = useLikeStore();

        return {
            :
            likeStore
        }
    },
    :
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yQCWI/dJMcaiEio9c/Mqym9Yb87a32OcTkKxZ1S0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yQCWI/dJMcaiEio9c/Mqym9Yb87a32OcTkKxZ1S0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yQCWI/dJMcaiEio9c/Mqym9Yb87a32OcTkKxZ1S0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyQCWI%2FdJMcaiEio9c%2FMqym9Yb87a32OcTkKxZ1S0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>defineStore</category>
      <category>pinia</category>
      <category>store</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/335</guid>
      <comments>https://carrotweb.tistory.com/335#entry335comment</comments>
      <pubDate>Tue, 4 Aug 2026 17:21:11 +0900</pubDate>
    </item>
    <item>
      <title>[10] Frontend 개발 - 상태 관리</title>
      <link>https://carrotweb.tistory.com/334</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;상태 관리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러&amp;nbsp;하위&amp;nbsp;컴포넌트들이&amp;nbsp;동일한&amp;nbsp;데이터를&amp;nbsp;사용해야&amp;nbsp;할&amp;nbsp;경우 &lt;br /&gt;&amp;rarr; 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달해야 할 때 props를 사용합니다.&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;1181&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RnmkP/dJMcaccYpEL/Ra5O1WQRccfCpPt1tdSnjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RnmkP/dJMcaccYpEL/Ra5O1WQRccfCpPt1tdSnjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RnmkP/dJMcaccYpEL/Ra5O1WQRccfCpPt1tdSnjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRnmkP%2FdJMcaccYpEL%2FRa5O1WQRccfCpPt1tdSnjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1181&quot; height=&quot;501&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깊은 계층 구조를 가진 컴포넌트 트리에서 비효율적입니다. &amp;rarr; Prop Drillin&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prop Drilling (프랍(프롭) 드릴링)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트는 부모 컴포넌트로부터 데이터를 전달 받기 위해 &amp;lt;script&amp;gt;에 props(프랍스, property, 프라퍼티)에 속성 명과 타입을 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모&amp;nbsp;컴포넌트는&amp;nbsp;&amp;lt;template&amp;gt;에서&amp;nbsp;자식&amp;nbsp;HTML&amp;nbsp;태그&amp;nbsp;안에&amp;nbsp;넘겨줄&amp;nbsp;prop을&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;RootComp.vue&lt;/p&gt;
&lt;pre id=&quot;code_1785829266835&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;h1&amp;gt;Hello, Vue!&amp;lt;/h1&amp;gt;
    &amp;lt;FooterComp userID=&quot;testid&quot; /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import FooterComp from './FooterComp.vue'

export default {
    name : 'RootComp',
    components : {
        FooterComp
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FooterComp.vue&lt;/p&gt;
&lt;pre id=&quot;code_1785829286372&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;DeepChildComp :userID=useID /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import DeepChildComp from './DeepChildComp.vue'

export default {
    name : 'FooterComp',
    components : {
        DeepChildComp
    },
    props: ['userID']
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Footer에는&amp;nbsp;사용하지&amp;nbsp;않지만&amp;nbsp;자식&amp;nbsp;컴포넌트인&amp;nbsp;DeepChild가&amp;nbsp;사용하기&amp;nbsp;때문에&amp;nbsp;prop를&amp;nbsp;설정해야&amp;nbsp;합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeepChildComp.vue&lt;/p&gt;
&lt;pre id=&quot;code_1785829331695&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;h2&amp;gt;{{ userID }}, Have a good time&amp;lt;/h2&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
    name : 'DeepChildComp',
    props: ['userID']
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Provide(제공) / Inject(주입) 사용 &amp;larr; &lt;span style=&quot;color: #000000;&quot;&gt;Prop Drilling &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 컴포넌트는 하위 모든 컴포넌트에 대한 의존성 제공자 역할을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위&amp;nbsp;모든&amp;nbsp;컴포넌트는&amp;nbsp;깊이에&amp;nbsp;관계없이&amp;nbsp;상위&amp;nbsp;체인의&amp;nbsp;컴포넌트에서&amp;nbsp;제공(provide)하는&amp;nbsp;의존성을&amp;nbsp;주입(inject)할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OEnfN/dJMcagNjkCn/SpwsLmCeQz7pFM0BIJhPfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OEnfN/dJMcagNjkCn/SpwsLmCeQz7pFM0BIJhPfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OEnfN/dJMcagNjkCn/SpwsLmCeQz7pFM0BIJhPfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOEnfN%2FdJMcagNjkCn%2FSpwsLmCeQz7pFM0BIJhPfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1181&quot; height=&quot;501&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위 모든 컴포넌트에 데이터를 제공하려면 provide() 함수를 사용&lt;/li&gt;
&lt;li&gt;상위&amp;nbsp;컴포넌트에서&amp;nbsp;제공하는&amp;nbsp;데이터를&amp;nbsp;주입하려면&amp;nbsp;inject()&amp;nbsp;함수를&amp;nbsp;사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위&amp;nbsp;모든&amp;nbsp;컴포넌트에&amp;nbsp;데이터를&amp;nbsp;제공하기&amp;nbsp;위해서&amp;nbsp;provide()&amp;nbsp;함수&amp;nbsp;또는&amp;nbsp;provide&amp;nbsp;옵션을&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785829485979&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { provide } from 'vue'

export default {
  setup() {
    provide('message', '안녕!')
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위&amp;nbsp;컴포넌트에서&amp;nbsp;제공하는&amp;nbsp;데이터를&amp;nbsp;주입하려면&amp;nbsp;inject()&amp;nbsp;함수&amp;nbsp;또는&amp;nbsp;inject&amp;nbsp;옵션을&amp;nbsp;사용합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785829535773&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { inject } from 'vue'

export default {
  setup() {
    const message = inject('message')
    return {
      message
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API 대신 Options API로 하면 개발하면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785829581749&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default {
  provide: {
    message: '안녕!'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785829590374&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default {
  inject: ['message'],
  data() {
    return {
      fullMessage: this.message
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Provide / Inject 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\main.js에 provide(프로바이드, 제공) 함수를 추가합니다. &amp;rarr; App-level&amp;nbsp;Provide&lt;/p&gt;
&lt;pre id=&quot;code_1785829681207&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue';
import { ref } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);

app.provide('appTitle', '점심 메뉴 앱');

const greetingMessage = ref('즐거운 점심 시간되세요!');
const likeCount = ref(0);

function likeUp() {
    this.likeCount++;
}

app.provide('useCommonStore', {
    greetingMessage,
    likeCount,
    likeUp
});

app.use(router).mount('#app');&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Provide할 때 반응형 데이터와 상태 변경에 대한 함수가 같이 있는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;&quot;use&quot;로&amp;nbsp;시작해&amp;nbsp;&quot;Store&quot;로&amp;nbsp;끝나는&amp;nbsp;Store의&amp;nbsp;이름을&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuView.vue에 provide(프로바이드, 제공) 함수를 추가합니다. &amp;rarr; Component&amp;nbsp;Provide&lt;/p&gt;
&lt;pre id=&quot;code_1785829795180&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;lunch-menu&quot;&amp;gt;점심 메뉴&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;lunch-view&quot;&amp;gt;
        &amp;lt;router-view /&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { provide } from 'vue';

export default {
    setup() {
        provide('lunchmenuTitle', '점심 메뉴&amp;rsquo;);
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\views\LunchMenu\LunchMenuListView.vue에&amp;nbsp;Injext(인젝트,&amp;nbsp;주입)&amp;nbsp;함수를&amp;nbsp;추가하고&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785829897152&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;{{ appTitle }} - {{ lunchmenuTitle }} 리스트&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;span&amp;gt;{{ greetingMessage }} &amp;lt;/span&amp;gt;
        &amp;lt;button @click=&quot;likeUp&quot;&amp;gt;좋아요 - {{ likeCount }}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    :
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
:
import { inject } from 'vue';

export default {
    setup() {
        :
        const appTitle = inject('appTitle');
        const { greetingMessage, likeCount, likeUp } = inject('useCommonStore');
        const lunchmenuTitle = inject('lunchmenuTitle');
        :
        return {
            :
            appTitle,
            greetingMessage,
            likeCount,
            likeUp,
            lunchmenuTitle
        }
    },
    :
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/list) &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wDexd/dJMcaa0wZb7/9kgkb47wIyKJ52gL1vYzYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wDexd/dJMcaa0wZb7/9kgkb47wIyKJ52gL1vYzYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wDexd/dJMcaa0wZb7/9kgkb47wIyKJ52gL1vYzYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwDexd%2FdJMcaa0wZb7%2F9kgkb47wIyKJ52gL1vYzYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;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;반응형&amp;nbsp;객체로&amp;nbsp;상태&amp;nbsp;관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태&amp;nbsp;관리 &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/scaling-up/state-management.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/scaling-up/state-management.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beDtPG/dJMcaftZYKU/NNOcQkKJiJSkwxOQG4szWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beDtPG/dJMcaftZYKU/NNOcQkKJiJSkwxOQG4szWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beDtPG/dJMcaftZYKU/NNOcQkKJiJSkwxOQG4szWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeDtPG%2FdJMcaftZYKU%2FNNOcQkKJiJSkwxOQG4szWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src에&amp;nbsp;stores&amp;nbsp;디렉터리를&amp;nbsp;생성하고&amp;nbsp;useCountStore.js&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9egR/dJMcafnjEYB/c7rirIT9IQR4D8wFoZefRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9egR/dJMcafnjEYB/c7rirIT9IQR4D8wFoZefRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9egR/dJMcafnjEYB/c7rirIT9IQR4D8wFoZefRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9egR%2FdJMcafnjEYB%2Fc7rirIT9IQR4D8wFoZefRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785830132392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { reactive } from 'vue';

export const useCountStore = reactive({
    // 카운트
    count: 0,
    // 카운트를 증가시킵니다.
    increment() {
        this.count++;
    },
    // 카운트를 감소시킵니다.
    decrement() {
        this.count--;
    },
    // 카운트를 랜덤으로 증가시킵니다.
    incrementByRandom() {
        const random = Math.floor(Math.random() * 5);
        this.count += random;
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reactive()를 사용하여 반응형 객체를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 데이터와 상태 변경에 대한 함수가 같이 있는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuView.vue에&amp;nbsp;useCountStore을&amp;nbsp;추가하고&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785830198702&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;lunch-menu&quot;&amp;gt;점심 메뉴
        &amp;lt;button @click=&quot;countStore.incrementByRandom()&quot;&amp;gt;{{ countStore.count }}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;lunch-view&quot;&amp;gt;
        &amp;lt;router-view /&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { provide } from 'vue';
import { useCountStore } from '@/stores/useCountStore.js'

export default {
    setup() {
        provide('lunchmenuTitle', '점심 메뉴');

        const countStore = useCountStore;

        return {
            countStore
        }
    }
}
&amp;lt;/script&amp;gt;

:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\views\LunchMenu\LunchMenuListView.vue에&amp;nbsp;useCountStore을&amp;nbsp;추가하고&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785830232085&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;{{ appTitle }} - {{ lunchmenuTitle }} 리스트
        &amp;lt;button @click=&quot;countStore.incrementByRandom()&quot;&amp;gt;{{ countStore.count }}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    :
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
:
import { useCountStore } from '@/stores/useCountStore.js'

export default {
    setup() {
        :
        const countStore = useCountStore;

        return {
            :
            countStore
        }
    },
    :
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/list)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MA3D1/dJMcaheluSh/vkaWhj7Tvv6BSwAlYBBsbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MA3D1/dJMcaheluSh/vkaWhj7Tvv6BSwAlYBBsbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MA3D1/dJMcaheluSh/vkaWhj7Tvv6BSwAlYBBsbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMA3D1%2FdJMcaheluSh%2FvkaWhj7Tvv6BSwAlYBBsbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;844&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>inject</category>
      <category>Prop Drilling</category>
      <category>provide</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/334</guid>
      <comments>https://carrotweb.tistory.com/334#entry334comment</comments>
      <pubDate>Tue, 4 Aug 2026 17:03:00 +0900</pubDate>
    </item>
    <item>
      <title>[9] Backend 개발 - 사용자 API 개발, 인증 처리, ID Token</title>
      <link>https://carrotweb.tistory.com/333</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ID&amp;nbsp;Token&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그인하면 서버에서 사용자를 확인하여 사용자 정보로 토큰을 생성한 후 응답 데이터로 클라이언트에게 토큰을 전달하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 요청을 보낼 때 Authorization HTTP Header에 토큰을 포함하여 전달하면 서버는 전달 받은 토큰을 검증하여 인증된 사용자인지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 토큰은 JSON Web Token (JWT, JSON 웹 토큰)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 토큰에는 인증된 사용자에 대한 정보(claim(클레임))가 포함되어 있습니다.&lt;/p&gt;
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트에서 토큰을 관리하기 때문에 서버에 부하가 낮아집니다.&lt;/li&gt;
&lt;li&gt;접근 권한을 적용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰이 탈취 당할 수 있습니다. &amp;rarr; 보안성이 취약함 (HTTPS로 보안할 수 있습니다. 단, 클라이언트 해킹은 차단할 수 없습니다.)&lt;/li&gt;
&lt;li&gt;모든 자원을 접근할 수 있습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;별도의 접근 권한 처리 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID&amp;nbsp;Token&amp;nbsp;Process&amp;nbsp;(ID&amp;nbsp;토큰&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;1459&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ATSLP/dJMcabkU3P1/XW9AP5kYcJu5lJCx84wdg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ATSLP/dJMcabkU3P1/XW9AP5kYcJu5lJCx84wdg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ATSLP/dJMcabkU3P1/XW9AP5kYcJu5lJCx84wdg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FATSLP%2FdJMcabkU3P1%2FXW9AP5kYcJu5lJCx84wdg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1459&quot; height=&quot;800&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;800&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;ID Token으로 JSON Web Token를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT(JSON&amp;nbsp;Web&amp;nbsp;Token&amp;nbsp;-&amp;nbsp;JSON&amp;nbsp;웹&amp;nbsp;토큰) &lt;br /&gt;(&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7519&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://datatracker.ietf.org/doc/html/rfc7519&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pc4E7/dJMcacKUfj9/KzpfcJe3m3APZ9j7jo2AnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pc4E7/dJMcacKUfj9/KzpfcJe3m3APZ9j7jo2AnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pc4E7/dJMcacKUfj9/KzpfcJe3m3APZ9j7jo2AnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpc4E7%2FdJMcacKUfj9%2FKzpfcJe3m3APZ9j7jo2AnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;849&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;849&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;JWT(JSON&amp;nbsp;Web&amp;nbsp;Token&amp;nbsp;-&amp;nbsp;JSON&amp;nbsp;웹&amp;nbsp;토큰) &lt;br /&gt;(&lt;a href=&quot;https://jwt.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jwt.io/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv6DNs/dJMcaijYVb8/kLqrzstaUDOAEcOuckZut1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv6DNs/dJMcaijYVb8/kLqrzstaUDOAEcOuckZut1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv6DNs/dJMcaijYVb8/kLqrzstaUDOAEcOuckZut1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv6DNs%2FdJMcaijYVb8%2FkLqrzstaUDOAEcOuckZut1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;847&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;847&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON&amp;nbsp;웹&amp;nbsp;토큰을&amp;nbsp;디코딩,&amp;nbsp;검증&amp;nbsp;그리고&amp;nbsp;생성합니다.&amp;nbsp;두&amp;nbsp;집단&amp;nbsp;간에&amp;nbsp;안전하게&amp;nbsp;클레임(정보)을&amp;nbsp;표현하기&amp;nbsp;위한&amp;nbsp;개방형&amp;nbsp;산업&amp;nbsp;표준&amp;nbsp;RFC&amp;nbsp;7519&amp;nbsp;방식입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;보호를&amp;nbsp;위해&amp;nbsp;모든&amp;nbsp;JWT&amp;nbsp;디버깅과&amp;nbsp;유효성&amp;nbsp;검사는&amp;nbsp;브라우저에서&amp;nbsp;수행됩니다.&amp;nbsp;JWT는&amp;nbsp;리소스에&amp;nbsp;대한&amp;nbsp;액세스&amp;nbsp;권한을&amp;nbsp;부여하는&amp;nbsp;자격&amp;nbsp;증명을&amp;nbsp;나타낼&amp;nbsp;수&amp;nbsp;있으므로&amp;nbsp;JWT를&amp;nbsp;붙여&amp;nbsp;넣거나&amp;nbsp;공유할&amp;nbsp;때&amp;nbsp;주의하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는&amp;nbsp;header(헤더).payload(페이로드,&amp;nbsp;전송되는&amp;nbsp;데이터).signature(시그니처,&amp;nbsp;서명)로&amp;nbsp;구성되어&amp;nbsp;있습니다. &lt;br /&gt;도트(.)로&amp;nbsp;구분되어&amp;nbsp;있습니다.&amp;nbsp;(Self-Contained&amp;nbsp;Token)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZM7Zj/dJMcafnjAPz/DH5mPrKhCBgKrjayNGeGuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZM7Zj/dJMcafnjAPz/DH5mPrKhCBgKrjayNGeGuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZM7Zj/dJMcafnjAPz/DH5mPrKhCBgKrjayNGeGuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZM7Zj%2FdJMcafnjAPz%2FDH5mPrKhCBgKrjayNGeGuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;848&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;848&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;JWT는&amp;nbsp;header.payload.signature로&amp;nbsp;구성되어&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;1419&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QcRr1/dJMcahk82E7/QiCwTMLeenGylfYo6i3NI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QcRr1/dJMcahk82E7/QiCwTMLeenGylfYo6i3NI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QcRr1/dJMcahk82E7/QiCwTMLeenGylfYo6i3NI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQcRr1%2FdJMcahk82E7%2FQiCwTMLeenGylfYo6i3NI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1419&quot; height=&quot;181&quot; data-origin-width=&quot;1419&quot; data-origin-height=&quot;181&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;b&gt;header(헤더)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;algorithm(alg, 서명에 사용된 알고리즘)과 token type(typ, 토큰 타입) 정보를 가지고 있습니다.&lt;/p&gt;
&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;payload(페이로드, 전송되는 데이터)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장되는 정보를 claim(클레임)이라고 부르고 name : value로 구성됩니다. 여러 claim 세트로 구성됩니다. (payload는 암호화되지 않기 때문에 개인정보는 사용하지 않아야 합니다.)&lt;/p&gt;
&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;signature(시그니처, 서명)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT의 무결성을 보장하기 위한 서명입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sign(header + &quot;.&quot; + payload, private_key)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 JSON은 base64UrlEncode()로 인코딩합니다.&lt;/p&gt;
&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;JWT&amp;nbsp;Claims &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Registered Claim Names (등록된 클레임 이름)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 이미 정의된 클레임입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iss (issuer, 이슈어, 발급자)&lt;/b&gt; : JWT 발급자 (JWT를 발급한 주체), 도메인을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex)&amp;nbsp;&quot;iss&quot;&amp;nbsp;:&amp;nbsp;&quot;auth.example.com&quot;,&amp;nbsp;&quot;iss&quot;&amp;nbsp;:&amp;nbsp;&quot;https://auth.example.com&quot;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sub (subject, 서브젝트, 제목)&lt;/b&gt; : JWT 제목&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex) &quot;sub&quot; : &quot;authcode&quot;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;aud (audience, 오디언스, 청취자)&lt;/b&gt; : JWT 대상자 (JWT를 받는 수신자), JWT를 요청한 클라이언트의 ID나 고유 식별 값을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex) &quot;aud&quot; : &quot;lunchmenuApp&quot;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;exp (expiration time, 엑스퍼레이션 타임, 만료 시간)&lt;/b&gt; : JWT 만료 시간, 시간 이후에는 JWT에 대한 처리가 수락되어서는 안됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex)&amp;nbsp;&quot;exp&quot;:&amp;nbsp;1629535586&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nbf (not before, 낫 비포, 이전 아님)&lt;/b&gt; : 시간 이전에는 JWT에 대한 처리가 수락되어서는 안됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex) &quot;nbf&quot;: 1629535596&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iat (Issued at, 이슈드 엣, 발급 시간)&lt;/b&gt; : JWT 발급 시간&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex) &quot;iat&quot;: 1629449186&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;jti (JWT ID)&lt;/b&gt; : JWT에 대한 고유 식별자, 발급자가 생성한 값 간의 충돌을 방지하기 위해 사용됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ex)&amp;nbsp;&quot;jti&quot;:&amp;nbsp;&quot;c56e9bf321b8406d9dac956652ec9aef&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Public Claim Names (공개 클레임 이름)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 이름으로 클레임 이름을 정의할 수 있습니다. 새로운 클레임 이름은 충돌을 방지하기 위해 충돌 방지 이름(Collision-Resistant Name, 컬리전 리지스턴트 네임)이 포함되어 있어야 합니다. 그래서 네임스페이스나 URI 형식으로 정의하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ex)&amp;nbsp;&quot;cohttp://m.example.auth.api.v1.member.userid&quot; : &quot;test1&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ex)&amp;nbsp;&quot;http://auth.example.com/api/v1/member/userid&quot; : &quot;test1&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Private Claim Names (비공개 클레임 이름)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 생산자(JWT 발급자)와 소비자(JWT를 요청한 클라이언트)는 합의하에 비공개 클레임 이름을 정의하고 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ex) &quot;name&quot; : &quot;kang byung joon&quot;, &quot;given_name&quot; : &quot;byung joon&amp;ldquo;, &quot;family_name&quot; : &quot;kang&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ex)&amp;nbsp;&quot;nickname&quot;&amp;nbsp;:&amp;nbsp;&quot;carrotweb&quot; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Libraries&amp;nbsp;for&amp;nbsp;Token&amp;nbsp;Signing/Verification &lt;br /&gt;(&lt;a href=&quot;https://jwt.io/libraries&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jwt.io/libraries&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o6omv/dJMcagGxwAQ/xWywhiTQuqZDzkHe5llIh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o6omv/dJMcagGxwAQ/xWywhiTQuqZDzkHe5llIh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o6omv/dJMcagGxwAQ/xWywhiTQuqZDzkHe5llIh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo6omv%2FdJMcagGxwAQ%2FxWywhiTQuqZDzkHe5llIh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;848&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;848&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;jsonwebtoken &lt;br /&gt;(&lt;a href=&quot;https://www.npmjs.com/package/jsonwebtoken&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/jsonwebtoken&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uUdL7/dJMcag0Gtcf/AY7K3s5Laeujk2ZVDUW850/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uUdL7/dJMcag0Gtcf/AY7K3s5Laeujk2ZVDUW850/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uUdL7/dJMcag0Gtcf/AY7K3s5Laeujk2ZVDUW850/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuUdL7%2FdJMcag0Gtcf%2FAY7K3s5Laeujk2ZVDUW850%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;846&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. JSON 토큰을 사용하기 위해 jsonwebtoken 설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785825801928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install jsonwebtoken --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZk4nd/dJMcaazv7HT/me2JTNnGr6UtP8oxERIkAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZk4nd/dJMcaazv7HT/me2JTNnGr6UtP8oxERIkAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZk4nd/dJMcaazv7HT/me2JTNnGr6UtP8oxERIkAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZk4nd%2FdJMcaazv7HT%2Fme2JTNnGr6UtP8oxERIkAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&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;uuid&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://www.npmjs.com/package/uuid&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/uuid&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCg7PO/dJMb998nrpS/6gbw7oHKzZGjl2TzVvWJ41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCg7PO/dJMb998nrpS/6gbw7oHKzZGjl2TzVvWJ41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCg7PO/dJMb998nrpS/6gbw7oHKzZGjl2TzVvWJ41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCg7PO%2FdJMb998nrpS%2F6gbw7oHKzZGjl2TzVvWJ41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;964&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. UUID를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;사용하기 위해&lt;span&gt; uuid &lt;/span&gt;&lt;/span&gt;설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785826008674&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install uuid --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G9v72/dJMcaidiczM/fz7PuFHaNEonjivrvjXwX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G9v72/dJMcaidiczM/fz7PuFHaNEonjivrvjXwX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G9v72/dJMcaidiczM/fz7PuFHaNEonjivrvjXwX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG9v72%2FdJMcaidiczM%2Ffz7PuFHaNEonjivrvjXwX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JWT&amp;nbsp;Sign&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785826158984&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jwt.sign(payload, secret, options, [callback])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;payload&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT에 저장되는 정보로 key : value로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 로그인 사용자를 구분하기 위해 아이디(sub), JWT 발급 시간(iat), JWT 만료 시간(exp), JWT 대상자(aud), JWT 발급자(iss), JWT에 대한 고유 식별자(jti), 이름(name), 권한(roles)을 저장합니다.&lt;/p&gt;
&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;secret&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서명을 만들 때 사용되는 암호 문자열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 UUID를 암호 문자열로 사용했습니다.&lt;/p&gt;
&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;options&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT를 생성할 때 사용되는 옵션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;algorithm은 암호화 알고리즘으로 HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384 등을 사용할 수 있습니다. 기본값으로 HS256를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;expiresIn(exp)는 JWT 만료 시간으로 초 단위 또는 시간 범위 문자열로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;년 단위 : &quot;1y&quot;, 일 단위 : &quot;1 days&quot;, &quot;1d&quot;, 시간 단위 : &quot;2.5 hrs&quot;, &quot;2h&quot;, 분 단위 : &quot;1m&quot;, 초 단위 : &quot;5s&amp;ldquo; (단위 문자가 포함되지 않으면 초 단위로 설정됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;notbefore(nbf)는 JWT를 처리하지 않는 시간으로 초 단위 또는 시간 범위 문자열로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;expiresIn와 동일한 표기법을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;audience(aud)는 JWT 대상자(JWT를 받는 수신자)입니다. JWT를 요청한 클라이언트의 ID나 고유 식별 값을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;issuer(iss)는 JWT 발급자(JWT를 발급한 주체)입니다. 도메인을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subject(sub)는 JWT 제목(claim의 주제)입니다. 인증에서는 사용자 ID를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwtid는 JWT 식별자입니다.&lt;/p&gt;
&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;[callback]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT가&amp;nbsp;생성된&amp;nbsp;후&amp;nbsp;호출되는&amp;nbsp;funcation입니다.&amp;nbsp;생성된&amp;nbsp;토큰(token)이나&amp;nbsp;에러(err)를&amp;nbsp;인자로&amp;nbsp;줍니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\api\v1\member.js&amp;nbsp;파일&amp;nbsp;전체&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785826318699&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const router = express.Router();
const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');
const { v4: uuidv4 } = require('uuid');
const jwt = require('jsonwebtoken');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

// 사용자 ID로 검색하여 사용자를 인증하고 리턴합니다.
router.post('/login', async function(req, res, next) {
    console.log(`REST API Post Method - Member Login.`);

    const result = {
        result: 'fail',
        code: 401,
        message: '',
        idtoken: ''
    };

    if (req.body.id == undefined || req.body.id == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;id가 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    } else if (req.body.password == undefined || req.body.password == &quot;&quot;) {
        result.code = 400;
        result.message = &quot;password가 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    const params = {
        id : req.body.id,
        password : req.body.password
    };

    //const query = &quot;SELECT * FROM tb_member WHERE mbr_id = ? AND mbr_pw = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
        'selectMember', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;
                const uuid = uuidv4().toString();
                const jwtid = uuid.replace(/-/gi, '');
                const secret = &quot;005c9780fe7c11eb89b4e39719de58a5&quot;;
                try {
                    const issuedAt = new Date().toLocaleString();
                    const iat = Date.parse(issuedAt) / 1000;
                    const idToken = await new Promise((resolve, reject) =&amp;gt; {
                        jwt.sign({
                                name : data[0].mbr_name,
                                roles : data[0].mbr_roles,
                                iat : iat
                            },
                            secret,
                            {
                                algorithm: &quot;HS256&quot;,
                                issuer : &quot;apiserver.example.com&quot;,
                                subject : data[0].mbr_id,
                                audience : &quot;lunchmenuApp&quot;,
                                expiresIn : &quot;1d&quot;,
                                jwtid : jwtid
                            },
                            (err, token) =&amp;gt; {
                                if (err) {
                                    reject(err);
                                } else {
                                    resolve(token);
                                }
                            });
                    });
                    result.result = &quot;success&quot;;
                    result.code = 200;
                    result.idtoken = idToken;
                } catch(err) {
                    console.log(err);
                    result.code = 500;
                    result.message = &quot;ID 토큰 생성이 실패하였습니다.&quot;;
                }
            }
        }
    } catch (error) {
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

module.exports = router;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JWT&amp;nbsp;Verify&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785826499958&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jwt.verify(token, secretOrPublicKey, [options, callback])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;token&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwt.sign() 메서드로 생성된 JWT입니다.&lt;/p&gt;
&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;secretOrPublicKey&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서명을 만들 때 사용된 암호 문자열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[callback]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT가 검증(확인)된 후 호출되는 funcation입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;payload&amp;nbsp;값(decode)이나&amp;nbsp;에러(err)를&amp;nbsp;인자로&amp;nbsp;줍니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\api\v1\authMiddleware.js&amp;nbsp;파일&amp;nbsp;전체&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785826563408&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const jwt = require('jsonwebtoken');

const authMiddleware = async (req, res, next) =&amp;gt; {
    let authorization = req.header('Authorization');

    const result = {
        result: 'fail',
        code: 401,
        message: '인증이 실패하였습니다.',
        data: {}
    };

    if (authorization == undefined || authorization == &quot;&quot;) {
        result.code = 400;
        res.status(result.code).json(result);
    } else {
        authorization = authorization.replace(&quot;Bearer &quot;,&quot;&quot;);
        try {
            const secret = &quot;005c9780fe7c11eb89b4e39719de58a5&quot;;
            const idToken = await new Promise((resolve, reject) =&amp;gt; {
                jwt.verify(authorization, secret, 
                    (err, decoded) =&amp;gt; {
                        if (err) {
                            reject(err);
                        } else {
                            resolve(decoded);
                        }
                    });
            });
            req.idToken = idToken;
            next();
        } catch(err) {
            res.status(result.code).json(result);
        }
    }
}

module.exports = authMiddleware;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. index.js&amp;nbsp;파일&amp;nbsp;수정&amp;nbsp;(쿠키,&amp;nbsp;세션&amp;nbsp;주석&amp;nbsp;처리) &lt;/p&gt;
&lt;pre id=&quot;code_1785826607801&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
//const cookieParser = require('cookie-parser');
//const session = require('express-session');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

:

// Cookie 설정
//app.use(cookieParser());

// 세션 설정
//app.use(session({
//    secret: &quot;test&quot;, // 세션 ID 쿠키에 서명하는데 사용되는 비밀번호입니다.
//    resave: false, // 세션을 세션 저장소에 다시 저장하도록 강제합니다.
//    saveUninitialized: true, // &quot;초기화되지 않은&quot; 세션을 강제로 저장소에 저장합니다.
//    name: &quot;sessionid&quot; // 응답에 설정할 세션 ID 쿠키의 이름입니다.
//}));

// 인증 설정
app.use('/api/v1/lunchmenus', require('./src/api/v1/authMiddleware'));
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인) &lt;br /&gt;1. 기존 쿠키 삭제&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd5Syn/dJMcacxiFcL/xt2Y46FN6mrptqJ05cAh00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd5Syn/dJMcacxiFcL/xt2Y46FN6mrptqJ05cAh00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd5Syn/dJMcacxiFcL/xt2Y46FN6mrptqJ05cAh00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd5Syn%2FdJMcacxiFcL%2Fxt2Y46FN6mrptqJ05cAh00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4cnCB/dJMcaalROq4/DcgwaIjeU8ku2LeUNYkLKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4cnCB/dJMcaalROq4/DcgwaIjeU8ku2LeUNYkLKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4cnCB/dJMcaalROq4/DcgwaIjeU8ku2LeUNYkLKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4cnCB%2FdJMcaalROq4%2FDcgwaIjeU8ku2LeUNYkLKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&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;3. 사용자 로그인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1F2kv/dJMcafU8ulz/GqyvyqyvTfKoSMpiL4xAsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1F2kv/dJMcafU8ulz/GqyvyqyvTfKoSMpiL4xAsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1F2kv/dJMcafU8ulz/GqyvyqyvTfKoSMpiL4xAsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1F2kv%2FdJMcafU8ulz%2FGqyvyqyvTfKoSMpiL4xAsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. JWT 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;913&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5V05h/dJMcahyBTeN/27VWgcjxFXlthrL3IZa5bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5V05h/dJMcahyBTeN/27VWgcjxFXlthrL3IZa5bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5V05h/dJMcahyBTeN/27VWgcjxFXlthrL3IZa5bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5V05h%2FdJMcahyBTeN%2F27VWgcjxFXlthrL3IZa5bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;913&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;913&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;5. 전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLNiOH/dJMcahL6Es1/qKzZDjrySdeN5K3EdLI9I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLNiOH/dJMcahL6Es1/qKzZDjrySdeN5K3EdLI9I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLNiOH/dJMcahL6Es1/qKzZDjrySdeN5K3EdLI9I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLNiOH%2FdJMcahL6Es1%2FqKzZDjrySdeN5K3EdLI9I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;844&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;844&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;&lt;b&gt;CORS 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cross Origin Resource Sharing (교차&amp;nbsp;출처&amp;nbsp;리소스&amp;nbsp;공유)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. index.js&amp;nbsp;파일&amp;nbsp;수정&amp;nbsp;(기존&amp;nbsp;CORS&amp;nbsp;처리에&amp;nbsp;Headers에&amp;nbsp;Authorization&amp;nbsp;추가,&amp;nbsp;OPTIONS&amp;nbsp;처리&amp;nbsp;추가)&lt;/p&gt;
&lt;pre id=&quot;code_1785826811174&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 모든 리소스에 접근 허용 - CORS(Cross-origin resource sharing, 교차 출처 리소스 공유) 문제 해결
// GET, POST, PUT, DELETE 메서드 사용 허용
// Content-type, Authorization 요청 허용
app.use(function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    if (req.method === 'OPTIONS') {
        res.sendStatus(200);
    } else {
        next();
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response to preflight request doesn't pass access control check: It does not have HTTP ok status.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preflight 요청에 대한 응답이 액세스 제어 검사를 통과하지 못했습니다. HTTP 확인 상태가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OPTIONS으로&amp;nbsp;요청이&amp;nbsp;들어오면&amp;nbsp;상태&amp;nbsp;코드&amp;nbsp;200으로&amp;nbsp;응답하면&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CORS(Cross&amp;nbsp;Origin&amp;nbsp;Resource&amp;nbsp;Sharing,&amp;nbsp;교차&amp;nbsp;출처&amp;nbsp;리소스&amp;nbsp;공유) &lt;br /&gt;(&lt;a href=&quot;https://expressjs.com/en/resources/middleware/cors.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://expressjs.com/en/resources/middleware/cors.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lqPyk/dJMcajb2g7s/XfZbk6leiqhU5ZGlJWg5u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lqPyk/dJMcajb2g7s/XfZbk6leiqhU5ZGlJWg5u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lqPyk/dJMcajb2g7s/XfZbk6leiqhU5ZGlJWg5u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlqPyk%2FdJMcajb2g7s%2FXfZbk6leiqhU5ZGlJWg5u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. cors를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;사용하기 위해&lt;span&gt;&lt;span&gt; cors&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785826884142&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install cors --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qoe3Q/dJMcaiKYHKF/9naoAeX0qEwff2fZASAZVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qoe3Q/dJMcaiKYHKF/9naoAeX0qEwff2fZASAZVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qoe3Q/dJMcaiKYHKF/9naoAeX0qEwff2fZASAZVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqoe3Q%2FdJMcaiKYHKF%2F9naoAeX0qEwff2fZASAZVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;825&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. index.js&amp;nbsp;파일&amp;nbsp;수정&amp;nbsp;(CORS&amp;nbsp;추가,&amp;nbsp;기존&amp;nbsp;CORS&amp;nbsp;주석&amp;nbsp;처리) &lt;/p&gt;
&lt;pre id=&quot;code_1785826920284&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
//const cookieParser = require('cookie-parser');
//const session = require('express-session');
const cors = require('cors');
const app = express();
const port = 9000;

app.use(cors());

:

// 모든 리소스에 접근 허용 - CORS(Cross-origin resource sharing, 교차 출처 리소스 공유) 문제 해결
// GET, POST, PUT, DELETE 메서드 사용 허용
// Content-type, Authorization 요청 허용
//app.use(function (req, res, next) {
//	res.header('Access-Control-Allow-Origin', '*');
//	res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
//	res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
//	if (req.method === 'OPTIONS') {
//		res.sendStatus(200);
//	} else {
//		next();
//	}
//});

:&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>CORS</category>
      <category>jsonwebtoken</category>
      <category>JWT Sign</category>
      <category>JWT Verify</category>
      <category>uuid</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/333</guid>
      <comments>https://carrotweb.tistory.com/333#entry333comment</comments>
      <pubDate>Tue, 4 Aug 2026 16:05:33 +0900</pubDate>
    </item>
    <item>
      <title>[8] Backend 개발 - 사용자 API 개발, 인증 처리, Session + HTTP Cookie</title>
      <link>https://carrotweb.tistory.com/332</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인증 처리를 위한 사용자 API 개발&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 처리를 위한 테이블을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Table 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;테이블 생성 쿼리 실행&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785819652340&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use lunchdb;

-- tb_member table이 있으면 삭제
DROP TABLE IF EXISTS tb_member;

-- tb_member table 생성
CREATE TABLE tb_member (
    seq int NOT NULL AUTO_INCREMENT COMMENT '사용자 일련번호',
    mbr_id varchar(50) NOT NULL COMMENT '사용자 ID',
    mbr_pw varchar(125) NOT NULL COMMENT '사용자 비밀번호',
    mbr_name varchar(50) NOT NULL COMMENT '사용자 이름',
    mbr_roles varchar(250) NOT NULL COMMENT '사용자 롤',
    reg_dtm datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일',
    CONSTRAINT tb_member_pk PRIMARY KEY (seq)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_general_ci
COMMENT='사용자';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;초기 데이터 등록 쿼리 실행&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785819731240&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO tb_member (mbr_id, mbr_pw, mbr_name, mbr_roles, reg_dtm) VALUES('test1', 'test1', '테스트1', 'ROLE_USER', '2025-03-06 14:12:00.000');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;사용자 API 개발&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;1. src\db\mappers\mariadb\lunchmenu에 member-sql.xml 파일을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WZHIp/dJMcabE6sTw/6RGKSKhVY3rvCSPfFKp1mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WZHIp/dJMcabE6sTw/6RGKSKhVY3rvCSPfFKp1mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WZHIp/dJMcabE6sTw/6RGKSKhVY3rvCSPfFKp1mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWZHIp%2FdJMcabE6sTw%2F6RGKSKhVY3rvCSPfFKp1mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785819808687&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;

&amp;lt;mapper namespace=&quot;apiserver.db.mappers.mariadb.lunchmenu.member&quot;&amp;gt;
    &amp;lt;select id=&quot;selectMember&quot;&amp;gt;
        SELECT *
          FROM tb_member
         WHERE mbr_id = #{id}
    &amp;lt;/select&amp;gt;
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1에&amp;nbsp;member.js&amp;nbsp;파일&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;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHso0j/dJMcafHB957/4Nzm3L1j1mZ4wVvQKZP0r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHso0j/dJMcafHB957/4Nzm3L1j1mZ4wVvQKZP0r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHso0j/dJMcafHB957/4Nzm3L1j1mZ4wVvQKZP0r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHso0j%2FdJMcafHB957%2F4Nzm3L1j1mZ4wVvQKZP0r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785819905964&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const router = express.Router();
const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

// 사용자 ID로 검색하여 사용자를 인증하고 리턴합니다.
router.post('/login', async function(req, res, next) {
    console.log(`REST API Post Method - Member Login.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    };

    if (req.body.id == undefined || req.body.id == &quot;&quot;) {
        result.result = &quot;fail&quot;;
        result.code = 400;
        result.message = &quot;id가 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    } else if (req.body.password == undefined || req.body.password == &quot;&quot;) {
        result.result = &quot;fail&quot;;
        result.code = 400;
        result.message = &quot;password가 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    const params = {
        id : req.body.id,
        password : req.body.password
    };

    //const query = &quot;SELECT * FROM tb_member WHERE mbr_id = ? AND mbr_pw = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
        'selectMember', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.result = &quot;fail&quot;;
            result.code = 401;
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = &quot;fail&quot;;
                result.code = 401;
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;
            }
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

module.exports = router;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. index.js&amp;nbsp;파일에&amp;nbsp;BODY를&amp;nbsp;JSON&amp;nbsp;데이터로&amp;nbsp;변환,&amp;nbsp;URL&amp;nbsp;인코딩,&amp;nbsp;요청&amp;nbsp;리소스&amp;nbsp;허용&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;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIoGf8/dJMcabrCrii/GOy9OvFWZQE7qLLkoCIKA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIoGf8/dJMcabrCrii/GOy9OvFWZQE7qLLkoCIKA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIoGf8/dJMcabrCrii/GOy9OvFWZQE7qLLkoCIKA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIoGf8%2FdJMcabrCrii%2FGOy9OvFWZQE7qLLkoCIKA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785820068051&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

:

// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () =&amp;gt; {
    console.log(`APIServer started on port ${port}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 API 테스트 (POSTMAN에서 확인) &lt;br /&gt;사용자 로그인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yGXg9/dJMcaixxOST/OKsXf70aBxPqK5X7rlh0Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yGXg9/dJMcaixxOST/OKsXf70aBxPqK5X7rlh0Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yGXg9/dJMcaixxOST/OKsXf70aBxPqK5X7rlh0Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyGXg9%2FdJMcaixxOST%2FOKsXf70aBxPqK5X7rlh0Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&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;&lt;b&gt;인증 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP Cookie (쿠키)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그인하면 서버에서 사용자를 확인하여 쿠키를 생성하고 응답 HTTP Header에 쿠키를 포함시켜 클라이언트에게 전달하는 방식입니다. (Set-Cookie: &amp;lt;cookie-name&amp;gt;=&amp;lt;cookie-value&amp;gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 요청을 보낼 때 요청 HTTP Header에 쿠키를 포함하여 전달하면 서버는 전달 받은 쿠키에서 세션을 가져와서 검증하고 인증된 사용자인지 확인합니다. (Cookie: &amp;lt;cookie-name&amp;gt;=&amp;lt;cookie-value&amp;gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트에 정보를 저장하기 때문에 서버에 부하가 낮아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키의 사이즈가 4KB이어서 많은 정보를 담을 수 없습니다.&lt;/li&gt;
&lt;li&gt;일반적으로 쿠키 값은 암호화 되지 않기 때문에 안전하지 않습니다.&lt;/li&gt;
&lt;li&gt;쿠키가 탈취 당할 수 있습니다.  보안성이 취약함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP&amp;nbsp;Cookie&amp;nbsp;Process&amp;nbsp;(쿠키&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;1459&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzDVC7/dJMcadCOSz3/79tEFNNKT8KpzPtbqCirf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzDVC7/dJMcadCOSz3/79tEFNNKT8KpzPtbqCirf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzDVC7/dJMcadCOSz3/79tEFNNKT8KpzPtbqCirf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzDVC7%2FdJMcadCOSz3%2F79tEFNNKT8KpzPtbqCirf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1459&quot; height=&quot;800&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;800&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;cookie-parser&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://expressjs.com/en/resources/middleware/cookie-parser.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://expressjs.com/en/resources/middleware/cookie-parser.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAlFz/dJMcahFjRLu/DQ5pA1chK1WVsoZdr1e3j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAlFz/dJMcahFjRLu/DQ5pA1chK1WVsoZdr1e3j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAlFz/dJMcahFjRLu/DQ5pA1chK1WVsoZdr1e3j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAlFz%2FdJMcahFjRLu%2FDQ5pA1chK1WVsoZdr1e3j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;846&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;1. 쿠키 파서를 사용하기 위해 cookie-parser 설치 (터미널에서 실행합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785820490183&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install cookie-parser --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4pfKQ/dJMcab6d1gM/3nEpq54zLkVzYhJZkkK8L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4pfKQ/dJMcab6d1gM/3nEpq54zLkVzYhJZkkK8L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4pfKQ/dJMcab6d1gM/3nEpq54zLkVzYhJZkkK8L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4pfKQ%2FdJMcab6d1gM%2F3nEpq54zLkVzYhJZkkK8L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. index.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785820565716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

:

// Cookie 설정
app.use(cookieParser());

// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () =&amp;gt; {
    console.log(`APIServer started on port ${port}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\api\v1\member.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785820624382&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    :
    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.code = 401;
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = &quot;fail&quot;;
                result.code = 401;
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;
                res.cookie(&quot;id&quot;, params.id);
                res.cookie(&quot;password&quot;, params.password);
            }
        }
    } catch (error) {
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;res.cookie로 정보를 쿠키에 저장합니다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러면 응답 HTTP Header에 쿠키를 포함시켜 클라이언트에게 전달됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785820755681&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;res.appendHeader(
    &quot;Set-Cookie&quot;, [&quot;id=&quot; + params.id, &quot;password=&quot; + params.password]
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 API 테스트 (POSTMAN에서 확인)&lt;br /&gt;사용자 로그인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QGOpK/dJMcajiTOuc/SaCwMciQEI1vTOkLkklez0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QGOpK/dJMcajiTOuc/SaCwMciQEI1vTOkLkklez0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QGOpK/dJMcajiTOuc/SaCwMciQEI1vTOkLkklez0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQGOpK%2FdJMcajiTOuc%2FSaCwMciQEI1vTOkLkklez0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&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;1082&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X8UYb/dJMcaiqH1d5/fsYUF0kGbziyCfTjXaJzg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X8UYb/dJMcaiqH1d5/fsYUF0kGbziyCfTjXaJzg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X8UYb/dJMcaiqH1d5/fsYUF0kGbziyCfTjXaJzg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX8UYb%2FdJMcaiqH1d5%2FfsYUF0kGbziyCfTjXaJzg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1082&quot; height=&quot;201&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인증 미들웨어(Middleware) 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1에&amp;nbsp;authMiddleware.js&amp;nbsp;파일&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;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJvmvP/dJMcajiTPdy/abXSeWi4MZRbjevmlKKSA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJvmvP/dJMcajiTPdy/abXSeWi4MZRbjevmlKKSA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJvmvP/dJMcajiTPdy/abXSeWi4MZRbjevmlKKSA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJvmvP%2FdJMcajiTPdy%2FabXSeWi4MZRbjevmlKKSA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785821578597&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

const authMiddleware = async (req, res, next) =&amp;gt; {
    const id = req.cookies.id;
    const password = req.cookies.password;

    const result = {
        result: 'fail',
        code: 400,
        message: '인증이 실패하였습니다.',
        data: {}
    };

    if ((id == undefined || id == &quot;&quot;) || (password == undefined || password == &quot;&quot;)) {
        res.status(result.code).json(result);
    } else {
        const params = {
            id : id,
            password : password
        };

        const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
            'selectMember', params, format);

        let conn;
        try {
            conn = await getDBConnection();
            data = await conn.query(query);
            if (data.length == 1) {
                if (data[0].mbr_pw == params.password) {
                    next();
                }
            }
        } catch (error) {
            result.code = 500;
            if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
                result.message = &quot;테이블이 없습니다.&quot;;
            } else {
                result.message = &quot;서버 오류가 발생하였습니다.&quot;;
            }
            res.status(result.code).json(result);
        } finally {
            if (conn) {
                conn.release();
            }
        }
    }
}

module.exports = authMiddleware;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. index.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785821599979&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

:

// Cookie 설정
app.use(cookieParser());

// 인증 설정
app.use('/api/v1/lunchmenus', require('./src/api/v1/authMiddleware'));
// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () =&amp;gt; {
    console.log(`APIServer started on port ${port}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 API 테스트 (POSTMAN에서 확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기존 쿠키 삭제&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OM3dF/dJMcahk81en/Ij4VAg5Ke2g3m7jNDqUJU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OM3dF/dJMcahk81en/Ij4VAg5Ke2g3m7jNDqUJU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OM3dF/dJMcahk81en/Ij4VAg5Ke2g3m7jNDqUJU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOM3dF%2FdJMcahk81en%2FIj4VAg5Ke2g3m7jNDqUJU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciu0JT/dJMcajiTQUH/Ks1hHFg1xaW44X9k0HikFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciu0JT/dJMcajiTQUH/Ks1hHFg1xaW44X9k0HikFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciu0JT/dJMcajiTQUH/Ks1hHFg1xaW44X9k0HikFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fciu0JT%2FdJMcajiTQUH%2FKs1hHFg1xaW44X9k0HikFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&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;3. 사용자&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;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYBJi/dJMcaa7nQOC/DeaCKTE8vWHeyqdaT7WC3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYBJi/dJMcaa7nQOC/DeaCKTE8vWHeyqdaT7WC3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYBJi/dJMcaa7nQOC/DeaCKTE8vWHeyqdaT7WC3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYBJi%2FdJMcaa7nQOC%2FDeaCKTE8vWHeyqdaT7WC3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnlVIW/dJMcafANoAW/De6DtWemsWV7SfuFC6PSp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnlVIW/dJMcafANoAW/De6DtWemsWV7SfuFC6PSp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnlVIW/dJMcafANoAW/De6DtWemsWV7SfuFC6PSp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnlVIW%2FdJMcafANoAW%2FDe6DtWemsWV7SfuFC6PSp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Session + HTTP Cookie(세션&amp;nbsp;+&amp;nbsp;쿠키)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그인하면 서버에서 사용자를 확인하여 세션을 생성하고 사용자 정보를 세션에 저장한 후 응답 HTTP Header에 쿠키를 포함시켜 클라이언트에게 전달하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 요청을 보낼 때 Cookie HTTP Header에 세션을 포함하여 전달하면 서버는 전달 받은 쿠키에서 세션을 가져와서 검증하고 인증된 사용자인지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에서 세션을 관리하기 때문에 안전합니다. &amp;rarr; 보안성이 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 정보를 관리하기 위해 서버 메모리가 많이 사용됩니다.&lt;/li&gt;
&lt;li&gt;서버를 확장할 경우 세션 관리가 어려워집니다. &amp;rarr; 서버 이중화를 위해 (In-Memory)DB를 사용&lt;/li&gt;
&lt;li&gt;쿠키가 탈취 당할 수 있습니다. &amp;rarr; 보안성이 취약함&lt;/li&gt;
&lt;li&gt;모든 자원을 접근할 수 있습니다. &amp;rarr; 별도의 접근 권한 처리 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session&amp;nbsp;+&amp;nbsp;HTTP&amp;nbsp;Cookie&amp;nbsp;Process&amp;nbsp;(세션&amp;nbsp;+&amp;nbsp;쿠키&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;1459&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DwyhQ/dJMcadbNxFC/9w9duLnJKyki528EXccSR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DwyhQ/dJMcadbNxFC/9w9duLnJKyki528EXccSR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DwyhQ/dJMcadbNxFC/9w9duLnJKyki528EXccSR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDwyhQ%2FdJMcadbNxFC%2F9w9duLnJKyki528EXccSR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1459&quot; height=&quot;800&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;800&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;express-session &lt;br /&gt;(&lt;a href=&quot;https://expressjs.com/en/resources/middleware/session.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://expressjs.com/en/resources/middleware/session.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/taQxz/dJMcabkU2F5/Swx2HyJ4pxka0QvyBs7VA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/taQxz/dJMcabkU2F5/Swx2HyJ4pxka0QvyBs7VA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/taQxz/dJMcabkU2F5/Swx2HyJ4pxka0QvyBs7VA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtaQxz%2FdJMcabkU2F5%2FSwx2HyJ4pxka0QvyBs7VA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;846&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 섹션&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;을 사용하기 위해 express-session 설치 (터미널에서 실행합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785823852839&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install express-session --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsQZQT/dJMcabkU2Gu/ILmCYVoUJeKJYw9siVaHX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsQZQT/dJMcabkU2Gu/ILmCYVoUJeKJYw9siVaHX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsQZQT/dJMcabkU2Gu/ILmCYVoUJeKJYw9siVaHX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsQZQT%2FdJMcabkU2Gu%2FILmCYVoUJeKJYw9siVaHX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. index.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785823982659&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

:

// Cookie 설정
app.use(cookieParser());

// 세션 설정
app.use(session({
    secret: &quot;test&quot;, // 세션 ID 쿠키에 서명하는데 사용되는 비밀번호입니다.
    resave: false, // 세션을 세션 저장소에 다시 저장하도록 강제합니다.
    saveUninitialized: true, // &quot;초기화되지 않은&quot; 세션을 강제로 저장소에 저장합니다.
    name: &quot;sessionid&quot; // 응답에 설정할 세션 ID 쿠키의 이름입니다.
}));

// 인증 설정
app.use('/api/v1/lunchmenus', require('./src/api/v1/authMiddleware'));
:
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\api\v1\member.js 파일 수정&lt;/p&gt;
&lt;pre id=&quot;code_1785824117632&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    :
    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.code = 401;
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = &quot;fail&quot;;
                result.code = 401;
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;
                req.session.userid = params.id;
                req.session.userpassword = params.password;
            }
        }
    } catch (error) {
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;req.session로&amp;nbsp;정보를&amp;nbsp;세션에&amp;nbsp;저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\api\v1\authMiddleware.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785824262251&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

const authMiddleware = async (req, res, next) =&amp;gt; {
    const id = req.session.userid;
    const password = req.session.userpassword;

    const result = {
        result: 'fail',
        code: 400,
        message: '인증이 실패하였습니다.',
        data: {}
    };
   :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 API 테스트 (POSTMAN에서 확인)&lt;br /&gt;1.&amp;nbsp;기존&amp;nbsp;쿠키&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;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byou8Y/dJMcadv46gU/ed6h3RxG6M98tKo6csSk21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byou8Y/dJMcadv46gU/ed6h3RxG6M98tKo6csSk21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byou8Y/dJMcadv46gU/ed6h3RxG6M98tKo6csSk21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyou8Y%2FdJMcadv46gU%2Fed6h3RxG6M98tKo6csSk21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0BWn1/dJMcadXgYgs/UGxy2tq3T6INshvICIpBek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0BWn1/dJMcadXgYgs/UGxy2tq3T6INshvICIpBek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0BWn1/dJMcadXgYgs/UGxy2tq3T6INshvICIpBek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0BWn1%2FdJMcadXgYgs%2FUGxy2tq3T6INshvICIpBek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&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;3. 사용자&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;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCMwm/dJMcadXgYgS/SKmU2zyDIWzmg1Vj03GktK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCMwm/dJMcadXgYgS/SKmU2zyDIWzmg1Vj03GktK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCMwm/dJMcadXgYgS/SKmU2zyDIWzmg1Vj03GktK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCMwm%2FdJMcadXgYgS%2FSKmU2zyDIWzmg1Vj03GktK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coomRi/dJMcadJEaQF/zBoyD2gvJFsNU6OziFeej0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coomRi/dJMcadJEaQF/zBoyD2gvJFsNU6OziFeej0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coomRi/dJMcadJEaQF/zBoyD2gvJFsNU6OziFeej0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoomRi%2FdJMcadJEaQF%2FzBoyD2gvJFsNU6OziFeej0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;843&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자 id와 password 대신 데이터로 저장&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;src\api\v1\member.js&amp;nbsp;파일&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785824555468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    :
    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.code = 401;
            result.message = &quot;사용자가 없습니다.&quot;;
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = &quot;fail&quot;;
                result.code = 401;
                result.message = &quot;비밀번호가 동일하지 않습니다.&quot;
            } else {
                result.message = &quot;사용자가 인증되었습니다.&quot;;
                req.session.member = data[0];
            }
        }
    } catch (error) {
    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\authMiddleware.js&amp;nbsp;파일&amp;nbsp;전체&amp;nbsp;수정&lt;/p&gt;
&lt;pre id=&quot;code_1785824606423&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const authMiddleware = async (req, res, next) =&amp;gt; {
    const member = req.session.member;

    const result = {
        result: 'fail',
        code: 400,
        message: '인증이 실패하였습니다.',
        data: {}
    };

    if (member == undefined || member == &quot;&quot;) {
        res.status(result.code).json(result);
    } else {
        next();
    }
}

module.exports = authMiddleware;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>Auth Middleware</category>
      <category>cookie-parser</category>
      <category>HTTP cookie</category>
      <category>Session</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/332</guid>
      <comments>https://carrotweb.tistory.com/332#entry332comment</comments>
      <pubDate>Tue, 4 Aug 2026 15:28:12 +0900</pubDate>
    </item>
    <item>
      <title>[7] Frontend 개발 - 읽기 뷰 (ReadView), 수정, 삭제 생성</title>
      <link>https://carrotweb.tistory.com/331</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;읽기&amp;nbsp;뷰&amp;nbsp;(ReadView)&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\views\LunchMenu에서 LunchMenuReadView.vue 파일을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MFO8e/dJMcafgvV2i/XLrrnxkcv4aKccjaPIs7V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MFO8e/dJMcafgvV2i/XLrrnxkcv4aKccjaPIs7V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MFO8e/dJMcafgvV2i/XLrrnxkcv4aKccjaPIs7V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMFO8e%2FdJMcafgvV2i%2FXLrrnxkcv4aKccjaPIs7V0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785740153554&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 보기&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
    setup() {
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\router\index.js에서&amp;nbsp;LunchMenuReadView.vue를&amp;nbsp;import하고&amp;nbsp;children(칠드런)에&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r2WFr/dJMcahFjfYF/tCjMuwMq7RxKu66Ivhz7Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r2WFr/dJMcahFjfYF/tCjMuwMq7RxKu66Ivhz7Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r2WFr/dJMcahFjfYF/tCjMuwMq7RxKu66Ivhz7Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr2WFr%2FdJMcahFjfYF%2FtCjMuwMq7RxKu66Ivhz7Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785740182145&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import LunchMenuReadView from '@/views/LunchMenu/LunchMenuReadView.vue'

const routes = [
  :
  {
    path: '/lunchmenu',
    component: LunchMenuView,
    children : [
      :
      {
        path: ':seq',
        name: 'lunchmenuread',
        component: LunchMenuReadView
      }
    ]
  }
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:seq는 웹 경로에 있는 파라미터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터는 콜론 :으로 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/lunchmenu/1 &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 경로에 있는 파라미터(동적 세그먼트)를 사용한 동적으로 라우팅하게 합니다. &amp;rarr; 파라미터를 사용한 동적 라우트 매칭 (&lt;a href=&quot;https://router.vuejs.kr/guide/essentials/dynamic-matching.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/essentials/dynamic-matching.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일에서&amp;nbsp;&amp;lt;li&amp;gt;가&amp;nbsp;클릭되면&amp;nbsp;함수(핸들러)가&amp;nbsp;호출되도록&amp;nbsp;@click(v-on:click)를&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;1241&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmB5He/dJMb99UPR9b/guGM53bQEKkPpDmsoLFnL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmB5He/dJMb99UPR9b/guGM53bQEKkPpDmsoLFnL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmB5He/dJMb99UPR9b/guGM53bQEKkPpDmsoLFnL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmB5He%2FdJMb99UPR9b%2FguGM53bQEKkPpDmsoLFnL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;824&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785740281798&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;router-link :to=&quot;{ name: 'lunchmenuadd' }&quot; class=&quot;button blue&quot;&amp;gt;
                &amp;lt;span&amp;gt;등록&amp;lt;/span&amp;gt;
            &amp;lt;/router-link&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-if=&quot;lunchMenuList.length == 0&quot;&amp;gt;
        점심 메뉴가 없습니다.
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-else&amp;gt;
        점심 메뉴가 {{ lunchMenuList.length }} 개 있습니다.
        &amp;lt;ul class=&quot;menu-list&quot;&amp;gt;
            &amp;lt;li class=&quot;menu-item&quot; v-for=&quot;(lunchMenuItem, index) in lunchMenuList&quot; :key=&quot;index&quot;
                @click=&quot;gotoLunchMenuRead(lunchMenuItem)&quot;&amp;gt;{{ lunchMenuItem.menu_name }}
            &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. setup()에 &amp;lt;li&amp;gt;가 클릭되면 읽기 뷰 컴포넌트로 이동하기 위해서 라우터 인스턴스를 받고 router.push() 함수를 사용합니다. &amp;rarr; Router 프로그램 방식&lt;/p&gt;
&lt;pre id=&quot;code_1785740314094&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import { useRouter } from 'vue-router';

export default {
    name : 'LunchMenuListView',
    setup() {
        const router = useRouter();
        :
        function gotoLunchMenuRead(lunchMenuItem) {
            router.push({name : 'lunchmenuread', params : {seq : lunchMenuItem.seq}});
        }

        return {
            :
            gotoLunchMenuRead
        }
    },
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name에는 route(라우트)를 생성할 때 부여한 이름을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;params에는 웹 경로에 있는 파라미터(콜론(:)으로 표시)인 :seq에 전달할 lunchMenuItem의 seq입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router.push()&amp;nbsp;함수를&amp;nbsp;사용하면&amp;nbsp;routers에&amp;nbsp;등록되어&amp;nbsp;있는&amp;nbsp;router를&amp;nbsp;찾아&amp;nbsp;이동하게&amp;nbsp;됩니다. &lt;br /&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/essentials/navigation.html#Navigate-to-a-different-location&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/essentials/navigation.html#Navigate-to-a-different-location&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;4. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/1)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwgtGh/dJMcadbMTdA/fmES2HjKQHvwxT7bARkoN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwgtGh/dJMcadbMTdA/fmES2HjKQHvwxT7bARkoN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwgtGh/dJMcadbMTdA/fmES2HjKQHvwxT7bARkoN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwgtGh%2FdJMcadbMTdA%2FfmES2HjKQHvwxT7bARkoN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;5. src\views\LunchMenu\LunchMenuListView.vue 파일에서 &amp;lt;li&amp;gt;에 있는 점심 메뉴 명을 클릭되면 이동하도록 router-link를 추가합니다. &amp;rarr; Router 선언적 방식 &lt;/p&gt;
&lt;pre id=&quot;code_1785740426129&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;router-link :to=&quot;{ name: 'lunchmenuadd' }&quot; class=&quot;button blue&quot;&amp;gt;
                &amp;lt;span&amp;gt;등록&amp;lt;/span&amp;gt;
            &amp;lt;/router-link&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-if=&quot;lunchMenuList.length == 0&quot;&amp;gt;
        점심 메뉴가 없습니다.
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-else&amp;gt;
        점심 메뉴가 {{ lunchMenuList.length }} 개 있습니다.
        &amp;lt;ul class=&quot;menu-list&quot;&amp;gt;
            &amp;lt;li class=&quot;menu-item&quot; v-for=&quot;(lunchMenuItem, index) in lunchMenuList&quot; :key=&quot;index&quot;&amp;gt;
                &amp;lt;router-link :to=&quot;{name : 'lunchmenuread', params : {seq: lunchMenuItem.seq}}&quot;&amp;gt;
                    {{ lunchMenuItem.menu_name }}
                &amp;lt;/router-link&amp;gt;
            &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name에는 route(라우트)를 생성할 때 부여한 이름을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;params에는&amp;nbsp;웹&amp;nbsp;경로에&amp;nbsp;있는&amp;nbsp;파라미터(콜론(:)으로&amp;nbsp;표시)인&amp;nbsp;:seq에&amp;nbsp;전달할&amp;nbsp;lunchMenuItem의&amp;nbsp;seq입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;6. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwTmIQ/dJMcad3XISZ/OcTKsQUNNH7jbZlgxiN37k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwTmIQ/dJMcad3XISZ/OcTKsQUNNH7jbZlgxiN37k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwTmIQ/dJMcad3XISZ/OcTKsQUNNH7jbZlgxiN37k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwTmIQ%2FdJMcad3XISZ%2FOcTKsQUNNH7jbZlgxiN37k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;LunchMenuListView.vue 파일에 있는 &amp;lt;style&amp;gt;에 menu-item를 추가했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785740500411&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt;
.menu-item a {
    color: white;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. src\views\LunchMenu\LunchMenuReadView.vue 파일을 수정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oKIyp/dJMb99N0JTD/PCkiyveNEKYa797XCWAoz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oKIyp/dJMb99N0JTD/PCkiyveNEKYa797XCWAoz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oKIyp/dJMb99N0JTD/PCkiyveNEKYa797XCWAoz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoKIyp%2FdJMb99N0JTD%2FPCkiyveNEKYa797XCWAoz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785740650646&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 보기 {{ $route.params.seq }}&amp;lt;/div&amp;gt;
    {{ lunchMenuItem.menu_name }}
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref } from 'vue';
import { useRoute } from 'vue-router'
import axios from 'axios';

export default {
    setup() {
        const route = useRoute();

        const lunchMenuItem = ref({});

        function getLunchMenuItem() {
            axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot; + route.params.seq).then((res) =&amp;gt; {
                console.log(res);
                lunchMenuItem.value = res.data.data;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }

        return {
            lunchMenuItem,
            getLunchMenuItem
        }
    },
    mounted() {
        this.getLunchMenuItem();
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 라우트는 템플릿에서 $route로 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우트가 매칭되면 파라미터의 값은 컴포넌트에서 route.params로 노출됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/lunchmenu/:seq &amp;rarr; /lunchmenu/1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRoute() 함수는 현재 라우트를 반환 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;8. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/1)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tC1YR/dJMb991E5sl/d5EMAsCk0AmnEMGxNhmhW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tC1YR/dJMb991E5sl/d5EMAsCk0AmnEMGxNhmhW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tC1YR/dJMb991E5sl/d5EMAsCk0AmnEMGxNhmhW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtC1YR%2FdJMb991E5sl%2Fd5EMAsCk0AmnEMGxNhmhW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;9. 목록 버튼을 추가하고 버튼이 클릭되면 점심 메뉴 리스트로 이동하도록 함수를 생성하고 리턴합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mrfgz/dJMcai5fEhd/NuCuFFuESIkpk8CjqBtSu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mrfgz/dJMcai5fEhd/NuCuFFuESIkpk8CjqBtSu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mrfgz/dJMcai5fEhd/NuCuFFuESIkpk8CjqBtSu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmrfgz%2FdJMcai5fEhd%2FNuCuFFuESIkpk8CjqBtSu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785740779363&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    :
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;button class=&quot;button&quot; @click=&quot;listClick&quot;&amp;gt;목록&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { useRoute, useRouter } from 'vue-router'

export default {
    setup() {
        :
        const router = useRouter();
        :
        function listClick() {
            router.back();
        }

        return {
            :
            listClick
        }
    },
    :
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/1)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EhVuc/dJMcadCOkvK/5hee11FZ0YveKBrY8sLLw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EhVuc/dJMcadCOkvK/5hee11FZ0YveKBrY8sLLw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EhVuc/dJMcadCOkvK/5hee11FZ0YveKBrY8sLLw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEhVuc%2FdJMcadCOkvK%2F5hee11FZ0YveKBrY8sLLw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;&lt;b&gt;수정 생성 &amp;ndash; 등록 뷰 (AddView) 재사용&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\router\index.js에서&amp;nbsp;수정&amp;nbsp;라우트를&amp;nbsp;children(칠드런)에&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1tO2A/dJMcaf8wJFA/NgjJFvzbv8G4vkIY8VWKm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1tO2A/dJMcaf8wJFA/NgjJFvzbv8G4vkIY8VWKm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1tO2A/dJMcaf8wJFA/NgjJFvzbv8G4vkIY8VWKm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1tO2A%2FdJMcaf8wJFA%2FNgjJFvzbv8G4vkIY8VWKm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785741233154&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
const routes = [
  :
  {
    path: '/lunchmenu',
    component: LunchMenuView,
    children : [
      :
      {
        path: ':seq/edit',
        name: 'lunchmenuedit',
        component: LunchMenuAddView
       }
    ]
  }
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/lunchmenu/1/edit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는&amp;nbsp;등록&amp;nbsp;컴포넌트를&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 수정&amp;nbsp;버튼을&amp;nbsp;추가하고&amp;nbsp;버튼이&amp;nbsp;클릭되면&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;수정으로&amp;nbsp;이동하도록&amp;nbsp;함수를&amp;nbsp;생성하고&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qJjWz/dJMcaijYhOr/VRZ0UTDz1BA3vbAYzMkqzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qJjWz/dJMcaijYhOr/VRZ0UTDz1BA3vbAYzMkqzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qJjWz/dJMcaijYhOr/VRZ0UTDz1BA3vbAYzMkqzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqJjWz%2FdJMcaijYhOr%2FVRZ0UTDz1BA3vbAYzMkqzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785741283217&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    :
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;left&quot;&amp;gt;
            &amp;lt;button class=&quot;button&quot; @click=&quot;editClick&quot;&amp;gt;수정&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        :
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
:
export default {
    setup() {
        :
        function editClick() {
            var result = confirm(&quot;수정하시겠습니까?&quot;);
            if (result) {
                router.push({name : 'lunchmenuedit', params : {seq : route.params.seq}});
            }
        }
    return {
            :
            editClick
        }
    },
    :
    
    }
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
.buttons &amp;gt; div.left {
    position:absolute;
    height:32px;
    left:0;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/1)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMvJp/dJMcaiKX3BW/rYKs6JClnWlHrK83XAsinK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMvJp/dJMcaiKX3BW/rYKs6JClnWlHrK83XAsinK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMvJp/dJMcaiKX3BW/rYKs6JClnWlHrK83XAsinK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMvJp%2FdJMcaiKX3BW%2FrYKs6JClnWlHrK83XAsinK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\views\LunchMenu\LunchMenuAddView.vue&amp;nbsp;파일에서&amp;nbsp;$route.name으로&amp;nbsp;등록인지&amp;nbsp;수정인지&amp;nbsp;확인하여&amp;nbsp;메지지를&amp;nbsp;변경하고&amp;nbsp;버튼을&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;1241&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K3g4r/dJMcafOoA1g/C8kF8GqiSgQrPpWMDy6d6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K3g4r/dJMcafOoA1g/C8kF8GqiSgQrPpWMDy6d6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K3g4r/dJMcafOoA1g/C8kF8GqiSgQrPpWMDy6d6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK3g4r%2FdJMcafOoA1g%2FC8kF8GqiSgQrPpWMDy6d6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;824&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785741352391&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 {{ $route.name == 'lunchmenuedit' ? '수정' : '등록' }}&amp;lt;/div&amp;gt;
    &amp;lt;form class=&quot;lunchMenuForm&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;label for=&quot;lunchMenuName&quot;&amp;gt;점심 메뉴 명 :&amp;lt;/label&amp;gt;
            &amp;lt;input type=&quot;text&quot; id=&quot;lunchMenuName&quot; 
                placeholder=&quot;점심 메뉴 명을 입력하세요.&quot;
                v-model=&quot;lunchMenuName&quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;button v-if=&quot;$route.name == 'lunchmenuedit'&quot; class=&quot;button blue&quot; @click=&quot;editClick&quot;&amp;gt;수정&amp;lt;/button&amp;gt;
            &amp;lt;button v-else class=&quot;button blue&quot; @click=&quot;addClick&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
            &amp;lt;button class=&quot;button&quot; @click=&quot;cancelClick&quot;&amp;gt;취소&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 버튼이 클릭되면 axios의 put() 함수를 사용하여 APIServer에 점심 메뉴가 수정되도록 합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785741384603&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { reactive, toRefs, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
:

export default {
    setup() {
        :
        const route = useRoute();
        :
        function editClick() {
            if (state.lunchMenuName == &quot;&quot;) {
                alert(&quot;점심 메뉴 명을 입력하세요.&quot;);
                lunchMenuNameInput.value.focus();
                return;
            }
            var result = confirm(&quot;수정하시겠습니까?&quot;);
            if (result) {
                const lunchMenuItem = { menuName : state.lunchMenuName };
                axios.put(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;+ route.params.seq, lunchMenuItem).then(() =&amp;gt; {
                    router.replace({name : 'lunchmenuread', params : {seq: route.params.seq}};
                }).catch((err) =&amp;gt; {
                    console.log(err);
                });
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onMounted()에&amp;nbsp;콜백으로&amp;nbsp;getLunchMenuItem()&amp;nbsp;함수를&amp;nbsp;등록하여&amp;nbsp;APIServer에서&amp;nbsp;점심&amp;nbsp;메뉴를&amp;nbsp;가져오도록&amp;nbsp;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785741447402&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        function getLunchMenuItem() {
            axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot; + route.params.seq).then((res) =&amp;gt; {
                state.lunchMenuName = res.data.data.menu_name;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }
        :
        onMounted(()=&amp;gt;{
            if (route.name == &quot;lunchmenuedit&quot;) {
                getLunchMenuItem();
            }
        });
        return {
            :
            editClick
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/1/edit)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LgZfO/dJMcabdYyJu/zC7KvWiIpFBR1wxy51kmO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LgZfO/dJMcabdYyJu/zC7KvWiIpFBR1wxy51kmO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LgZfO/dJMcabdYyJu/zC7KvWiIpFBR1wxy51kmO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLgZfO%2FdJMcabdYyJu%2FzC7KvWiIpFBR1wxy51kmO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nb0Sb/dJMcabdYyJM/jP0Bw8qi0ykwsdaeHKdps1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nb0Sb/dJMcabdYyJM/jP0Bw8qi0ykwsdaeHKdps1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nb0Sb/dJMcabdYyJM/jP0Bw8qi0ykwsdaeHKdps1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnb0Sb%2FdJMcabdYyJM%2FjP0Bw8qi0ykwsdaeHKdps1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcDrmr/dJMcabdYyJ5/V2H2NOVtOsefFuQX9kGhbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcDrmr/dJMcabdYyJ5/V2H2NOVtOsefFuQX9kGhbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcDrmr/dJMcabdYyJ5/V2H2NOVtOsefFuQX9kGhbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcDrmr%2FdJMcabdYyJ5%2FV2H2NOVtOsefFuQX9kGhbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 마운트 된 후 점심 메뉴 명 입력란으로 포커스가 가기 위해서 &amp;lt;template&amp;gt;를 참조하여 처리해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785741563668&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    :
            &amp;lt;input type=&quot;text&quot; id=&quot;lunchMenuName&quot; placeholder=&quot;점심 메뉴 명을 입력하세요.&quot;
                v-model=&quot;lunchMenuName&quot;
                ref=&quot;lunchMenuNameInput&quot;/&amp;gt;
    :
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { &amp;hellip;, ref } from 'vue';

export default {
    setup() {
        :
        const lunchMenuNameInput = ref(null);
        :
        onMounted(()=&amp;gt;{
            :
            lunchMenuNameInput.value.focus();
        });
        
        return {
            :
            lunchMenuNameInput
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM에 접근하기 위해서 ref란 특수 속성을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref 속성 값과 일치하는 이름으로 ref를 선언해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값은 null 이어야 합니다. 마운트 된 후 DOM객체로 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;focus() 함수로 input에 포커스가 이동됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 참조에 대한 설명을 참고하세요. (&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/template-refs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/template-refs.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;삭제&amp;nbsp;&amp;ndash;&amp;nbsp;읽기&amp;nbsp;뷰&amp;nbsp;(ReadView)&amp;nbsp;사용&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 삭제 버튼을 추가하고 버튼이 클릭되면 점심 메뉴가 삭제되도록 함수를 생성하고 리턴합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rs8lc/dJMcadiIqSd/y2mUQhgp58Osetw8YnmPR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rs8lc/dJMcadiIqSd/y2mUQhgp58Osetw8YnmPR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rs8lc/dJMcadiIqSd/y2mUQhgp58Osetw8YnmPR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frs8lc%2FdJMcadiIqSd%2Fy2mUQhgp58Osetw8YnmPR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785741688501&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    :
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;left&quot;&amp;gt;
            &amp;lt;button class=&quot;button&quot; @click=&quot;editClick&quot;&amp;gt;수정&amp;lt;/button&amp;gt;
            &amp;lt;button class=&quot;button&quot; @click=&quot;deleteClick&quot;&amp;gt;삭제&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        :
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
:

export default {
    setup() {
        :
        async function deleteClick() {
            var result = confirm(&quot;삭제하시겠습니까?&quot;);
            if (result) {
                try {
                    const res = await axios.delete(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot; + route.params.seq);
                    if (res.data.code == 200) {
                        alert(&quot;삭제되었습니다.&quot;);
                        router.push({name : 'lunchmenulist&amp;rsquo;});
                    } else {
                        alert(&quot;삭제되지 않았습니다.&quot;);
                    }
                } catch(err) {
                    console.log(err);
                }
            }
        }

        return {
            :
            deleteClick
        }
    },
    :
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/4)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bhzk4/dJMcadXgiIN/LxhxFcZRfWei5R4yYoxek0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bhzk4/dJMcadXgiIN/LxhxFcZRfWei5R4yYoxek0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bhzk4/dJMcadXgiIN/LxhxFcZRfWei5R4yYoxek0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBhzk4%2FdJMcadXgiIN%2FLxhxFcZRfWei5R4yYoxek0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yMeuI/dJMcafgvXj8/f0NbhGlEX3kKlKcqnd3wu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yMeuI/dJMcafgvXj8/f0NbhGlEX3kKlKcqnd3wu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yMeuI/dJMcafgvXj8/f0NbhGlEX3kKlKcqnd3wu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyMeuI%2FdJMcafgvXj8%2Ff0NbhGlEX3kKlKcqnd3wu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR6kIt/dJMcaf8wJ0Y/6l6nE6xXXz4Z5bYmWYaT8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR6kIt/dJMcaf8wJ0Y/6l6nE6xXXz4Z5bYmWYaT8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR6kIt/dJMcaf8wJ0Y/6l6nE6xXXz4Z5bYmWYaT8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR6kIt%2FdJMcaf8wJ0Y%2F6l6nE6xXXz4Z5bYmWYaT8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라우터와&amp;nbsp;현재&amp;nbsp;라우트에&amp;nbsp;접근하기&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;1376&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BPN4z/dJMcagl6PQL/KyiphWIUBD05Gtz12siHt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BPN4z/dJMcagl6PQL/KyiphWIUBD05Gtz12siHt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BPN4z/dJMcagl6PQL/KyiphWIUBD05Gtz12siHt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBPN4z%2FdJMcagl6PQL%2FKyiphWIUBD05Gtz12siHt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1376&quot; height=&quot;599&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터와&amp;nbsp;현재&amp;nbsp;라우트에&amp;nbsp;대한&amp;nbsp;접근&amp;nbsp;방법을&amp;nbsp;참고하세요. &lt;br /&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/#Accessing-the-router-and-current-route&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/#Accessing-the-router-and-current-route&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변수와&amp;nbsp;함수&amp;nbsp;사용&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;1376&quot; data-origin-height=&quot;855&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m0oO1/dJMcaa0wjt3/eBQVAdm5ay4YcGoxOcB2k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m0oO1/dJMcaa0wjt3/eBQVAdm5ay4YcGoxOcB2k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m0oO1/dJMcaa0wjt3/eBQVAdm5ay4YcGoxOcB2k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm0oO1%2FdJMcaa0wjt3%2FeBQVAdm5ay4YcGoxOcB2k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1376&quot; height=&quot;855&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;855&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;1376&quot; data-origin-height=&quot;855&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UJ2eH/dJMcaazvq3u/qcEnDRUusf73BiAS6N5NIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UJ2eH/dJMcaazvq3u/qcEnDRUusf73BiAS6N5NIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UJ2eH/dJMcaazvq3u/qcEnDRUusf73BiAS6N5NIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUJ2eH%2FdJMcaazvq3u%2FqcEnDRUusf73BiAS6N5NIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1376&quot; height=&quot;855&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;855&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;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>dynamic-matching</category>
      <category>node.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/331</guid>
      <comments>https://carrotweb.tistory.com/331#entry331comment</comments>
      <pubDate>Mon, 3 Aug 2026 16:29:23 +0900</pubDate>
    </item>
    <item>
      <title>[6] Frontend 개발 - 등록 뷰 (AddView) 생성</title>
      <link>https://carrotweb.tistory.com/330</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;등록 뷰 (AddView) 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\views\LunchMenu에서&amp;nbsp;LunchMenuAddView.vue&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAkVBH/dJMcajb1xli/3Bz2NXAydqrYBwIX3iKO31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAkVBH/dJMcajb1xli/3Bz2NXAydqrYBwIX3iKO31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAkVBH/dJMcajb1xli/3Bz2NXAydqrYBwIX3iKO31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAkVBH%2FdJMcajb1xli%2F3Bz2NXAydqrYBwIX3iKO31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785737418686&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 등록&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
    setup() {
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\router\index.js에서&amp;nbsp;LunchMenuAddView.vue를&amp;nbsp;import하고&amp;nbsp;routes에&amp;nbsp;route를&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDBjcM/dJMcajpwhKA/2iDbiQ4ygjkszbxfkPN5o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDBjcM/dJMcajpwhKA/2iDbiQ4ygjkszbxfkPN5o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDBjcM/dJMcajpwhKA/2iDbiQ4ygjkszbxfkPN5o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDBjcM%2FdJMcajpwhKA%2F2iDbiQ4ygjkszbxfkPN5o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785737451805&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import LunchMenuAddView from '@/views/LunchMenu/LunchMenuAddView.vue'

const routes = [
  :
  {
    path: '/lunchmenu/',
    name: 'lunchmenulist',
    component: LunchMenuListView
  },
  {
    path: '/lunchmenu/add',
    name: 'lunchmenuadd',
    component: LunchMenuAddView
  }
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중첩된&amp;nbsp;라우트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/lunchmenu/와&amp;nbsp;/lunchmenu/add를&amp;nbsp;App.vue에&amp;nbsp;있는&amp;nbsp;router-view&amp;nbsp;컴포넌트가&amp;nbsp;아닌&amp;nbsp;/lunchmenu에서&amp;nbsp;Vue&amp;nbsp;컴포넌트를&amp;nbsp;생성하고&amp;nbsp;자체적인&amp;nbsp;router-view&amp;nbsp;컴포넌트에서&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/ &amp;rarr; app.vue &lt;br /&gt;+-&amp;nbsp;&amp;lt;router-view/&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+- /lunchmenu/ &amp;rarr; LunchMenuListView.vue &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+- /lunchmenu/add &amp;rarr; LunchMenuAddView.vue &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/muSh1/dJMcahrSUoP/g0CyIMiFYnswhpKrCLjiwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/muSh1/dJMcahrSUoP/g0CyIMiFYnswhpKrCLjiwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/muSh1/dJMcahrSUoP/g0CyIMiFYnswhpKrCLjiwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmuSh1%2FdJMcahrSUoP%2Fg0CyIMiFYnswhpKrCLjiwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;188&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.vue에&amp;nbsp;있는&amp;nbsp;router-view에서&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;rarr; app.vue &lt;br /&gt;+-&amp;nbsp;&amp;lt;router-view/&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+- /lunchmenu &amp;rarr; LunchMenuView.vue &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+-&amp;nbsp;&amp;lt;router-view/&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+- / &amp;rarr; LunchMenuListView.vue &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+- /add &amp;rarr; LunchMenuAddView.vue &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uipvX/dJMcagfmGNL/N3vaOalx1YfCknkOcSobc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uipvX/dJMcagfmGNL/N3vaOalx1YfCknkOcSobc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uipvX/dJMcagfmGNL/N3vaOalx1YfCknkOcSobc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuipvX%2FdJMcagfmGNL%2FN3vaOalx1YfCknkOcSobc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;289&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LunchMenuView에&amp;nbsp;있는&amp;nbsp;router-view에서&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;라우트에&amp;nbsp;연결된&amp;nbsp;Vue&amp;nbsp;컴포넌트에는&amp;nbsp;반드시&amp;nbsp;router-view&amp;nbsp;컴포넌트가&amp;nbsp;있어야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App UI는 컴포넌트가 중첩되게 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹&amp;nbsp;경로(URL)를&amp;nbsp;세그먼트(segment,&amp;nbsp;부분)하면&amp;nbsp;컴포넌트를&amp;nbsp;중첩&amp;nbsp;시킬&amp;nbsp;수&amp;nbsp;있습니다.&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;1399&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WhWUR/dJMcag7tqd9/NXSRc7gekvYQwiyZkqvah0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WhWUR/dJMcag7tqd9/NXSRc7gekvYQwiyZkqvah0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WhWUR/dJMcag7tqd9/NXSRc7gekvYQwiyZkqvah0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWhWUR%2FdJMcag7tqd9%2FNXSRc7gekvYQwiyZkqvah0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1399&quot; height=&quot;518&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;518&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;라우트에&amp;nbsp;대한&amp;nbsp;설명을&amp;nbsp;참고하세요. &lt;br /&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/essentials/nested-routes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/essentials/nested-routes.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\views\LunchMenu에서&amp;nbsp;LunchMenuView.vue&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qDuMc/dJMcaftZexI/sW5qkTvqm7LbYlTtxVYwZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qDuMc/dJMcaftZexI/sW5qkTvqm7LbYlTtxVYwZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qDuMc/dJMcaftZexI/sW5qkTvqm7LbYlTtxVYwZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqDuMc%2FdJMcaftZexI%2FsW5qkTvqm7LbYlTtxVYwZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785737791590&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;lunch-menu&quot;&amp;gt;점심 메뉴&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;lunch-view&quot;&amp;gt;
        &amp;lt;router-view/&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style&amp;gt;
.lunch-menu {
    width: 540px;
    margin: 10px auto;
}
.lunch-view {
    background-color: #dddddd;
    width: 540px;
    padding: 12px 0px;
    margin: auto auto;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. src\router\index.js에서 LunchMenuView.vue를 import하고 중첩된 웹 경로(부모 경로)를 분리하여 route에 추가하고 중첩되지 않는 웹 경로(자식 경로)는 children(칠드런)에 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qErl3/dJMcadv4rMP/MzyLsmZyeydRJquJmzZink/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qErl3/dJMcadv4rMP/MzyLsmZyeydRJquJmzZink/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qErl3/dJMcadv4rMP/MzyLsmZyeydRJquJmzZink/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqErl3%2FdJMcadv4rMP%2FMzyLsmZyeydRJquJmzZink%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;824&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785737828672&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import LunchMenuView from '@/views/LunchMenu/LunchMenuView.vue'

const routes = [
  :
  {
    path: '/lunchmenu&amp;rsquo;,
    component: LunchMenuView,
    children : [
      {
        path: '',
        name: 'lunchmenulist',
        component: LunchMenuListView
      },
      {
        path: 'add',
        name: 'lunchmenuadd',
        component: LunchMenuAddView
      }
    ]
  }
]
:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mZblN/dJMcaiKX1Gp/aDbFFE2CQxwkEGkR4WZbCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mZblN/dJMcaiKX1Gp/aDbFFE2CQxwkEGkR4WZbCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mZblN/dJMcaiKX1Gp/aDbFFE2CQxwkEGkR4WZbCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmZblN%2FdJMcaiKX1Gp%2FaDbFFE2CQxwkEGkR4WZbCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1361&quot; height=&quot;716&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;716&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;5. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCQyym/dJMcadpmqMo/dZX2Z8DodlDzw6vfTbHSRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCQyym/dJMcadpmqMo/dZX2Z8DodlDzw6vfTbHSRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCQyym/dJMcadpmqMo/dZX2Z8DodlDzw6vfTbHSRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCQyym%2FdJMcadpmqMo%2FdZX2Z8DodlDzw6vfTbHSRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. src\views\LunchMenu\LunchMenuListView.vue&amp;nbsp;파일에&amp;nbsp;등록&amp;nbsp;뷰&amp;nbsp;컴포넌트로&amp;nbsp;이동하기&amp;nbsp;위해&amp;nbsp;router-link를&amp;nbsp;추가합니다.&amp;nbsp;&amp;lt;style&amp;gt;에&amp;nbsp;있는&amp;nbsp;menu-list를&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;1241&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EBnvi/dJMcab6dnS1/It6VYv6TDP5o9n0KbZIniK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EBnvi/dJMcab6dnS1/It6VYv6TDP5o9n0KbZIniK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EBnvi/dJMcab6dnS1/It6VYv6TDP5o9n0KbZIniK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEBnvi%2FdJMcab6dnS1%2FIt6VYv6TDP5o9n0KbZIniK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;824&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785738085596&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;router-link :to=&quot;{ name: 'lunchmenuadd' }&quot; class=&quot;button blue&quot;&amp;gt;
                &amp;lt;span&amp;gt;등록&amp;lt;/span&amp;gt;
            &amp;lt;/router-link&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    :
&amp;lt;/template&amp;gt;

&amp;lt;style&amp;gt;
.menu-list {
    margin: 10px 10px;
    padding: 0;
}
:
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;to 속성에 path(웹 경로) 대신 route(라우트) 이름을 사용하기 위해서는 :to 속성을 사용해야 합니다.&lt;/li&gt;
&lt;li&gt;:to는 v-bind:to를 단축한 겁니다. v-bind는 속성 값에 데이터를 바인딩해줍니다.&lt;/li&gt;
&lt;li&gt;name에는 route(라우트)를 생성할 때 부여한 이름을 사용합니다.&lt;/li&gt;
&lt;li&gt;/lunchmenu/add로&amp;nbsp;링크가&amp;nbsp;생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네임드&amp;nbsp;라우트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router-link&amp;nbsp;컴포넌트는&amp;nbsp;path&amp;nbsp;대신&amp;nbsp;route(라우트)를&amp;nbsp;생성할&amp;nbsp;때&amp;nbsp;부여한&amp;nbsp;이름으로&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;네임드 라우트 (&lt;a href=&quot;https://router.vuejs.kr/guide/essentials/named-routes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/essentials/named-routes.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;to 속성에 path(웹 경로)를 사용 &amp;rarr; route(라우트)를 생성할 때 등록한 path를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v3.x&lt;/p&gt;
&lt;pre id=&quot;code_1785738347195&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;router-link to=&quot;/lunchmenu/add&quot;&amp;gt;등록&amp;lt;/router-link&amp;gt;
&amp;lt;router-link :to=&quot;{ path: '/lunchmenu/add' }&quot;&amp;gt;등록&amp;lt;/RouterLink&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v4.x&lt;/p&gt;
&lt;pre id=&quot;code_1785738372941&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;RouterLink to=&quot;/lunchmenu/add&quot;&amp;gt;등록&amp;lt;/RouterLink&amp;gt;
&amp;lt;RouterLink :to=&quot;{ path: '/lunchmenu/add' }&quot;&amp;gt;등록&amp;lt;/RouterLink&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:to(v-bind:to) 속성에 name(이름)을 사용 &amp;rarr; route(라우트)를 생성할 때 부여한 이름을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v3.x&lt;/p&gt;
&lt;pre id=&quot;code_1785738411723&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;router-link :to=&quot;{ name: 'lunchmenuadd' }&quot;&amp;gt;등록&amp;lt;/router-link&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v4.x&lt;/p&gt;
&lt;pre id=&quot;code_1785738425029&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;RouterLink :to=&quot;{ name: 'lunchmenuadd' }&quot;&amp;gt;등록&amp;lt;/RouterLink&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. public에 css 디렉터리를 생성하고 default.css 파일을 생성합니다. 공통으로 사용할 버튼 CSS를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnJcdW/dJMcaafcyH9/nw0U3OeFOsvVIY2jHW36CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnJcdW/dJMcaafcyH9/nw0U3OeFOsvVIY2jHW36CK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnJcdW/dJMcaafcyH9/nw0U3OeFOsvVIY2jHW36CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnJcdW%2FdJMcaafcyH9%2Fnw0U3OeFOsvVIY2jHW36CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785738474410&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.buttons {
    position: relative;
    width: 500px; 
    height: 32px;
    margin-bottom: 20px;
    margin: auto auto;
}
.buttons &amp;gt; div.right {
    position: absolute;
    height: 32px;
    right: 0;
}
.buttons &amp;gt; div &amp;gt; a.button {
    display: inline-block;
    min-width: 95px;
    height: 32px;
    line-height: 32px;
    vertical-align: middle;
    font-size: 13px;
    text-align: center;
    text-decoration: none;
    border-radius: 20px;
    cursor: pointer;
}
.buttons &amp;gt; div &amp;gt; .button.blue {
    color: #fff; 
    border-color: #0099d2 !important; 
    background: #0099d2 !important;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. link로&amp;nbsp;스타일시트를&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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baSVT2/dJMcaiROLVs/VauKwqKWkDeRFAxruPQY0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baSVT2/dJMcaiROLVs/VauKwqKWkDeRFAxruPQY0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baSVT2/dJMcaiROLVs/VauKwqKWkDeRFAxruPQY0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaSVT2%2FdJMcaiROLVs%2FVauKwqKWkDeRFAxruPQY0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785738503240&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
    &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1.0&quot;&amp;gt;
    &amp;lt;link rel=&quot;icon&quot; href=&quot;&amp;lt;%= BASE_URL %&amp;gt;favicon.ico&quot;&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;&amp;lt;%= BASE_URL %&amp;gt;css/default.css&quot;&amp;gt;
    &amp;lt;title&amp;gt;&amp;lt;%= htmlWebpackPlugin.options.title %&amp;gt;&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;noscript&amp;gt;
      &amp;lt;strong&amp;gt;We're sorry but &amp;lt;%= htmlWebpackPlugin.options.title %&amp;gt; doesn't work properly without JavaScript enabled. Please enable it to continue.&amp;lt;/strong&amp;gt;
    &amp;lt;/noscript&amp;gt;
    &amp;lt;div id=&quot;app&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- built files will be auto injected --&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;%= BASE_URL %&amp;gt; 대신 /를 사용해도 됩니다. &amp;rarr; &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/default.css&quot;&amp;gt; &lt;br /&gt;vue.config.js에서&amp;nbsp;module.exports에&amp;nbsp;publicPath가&amp;nbsp;없으면&amp;nbsp;&amp;lt;%=&amp;nbsp;BASE_URL&amp;nbsp;%&amp;gt;은&amp;nbsp;/입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BASE_URL&amp;nbsp;변경&amp;nbsp;방법&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue.config.js에서&amp;nbsp;module.exports에&amp;nbsp;publicPath로&amp;nbsp;경로를&amp;nbsp;추가하고&amp;nbsp;다시&amp;nbsp;시작(npm&amp;nbsp;run&amp;nbsp;serve)하면&amp;nbsp;BASE_URL이&amp;nbsp;publicPath로&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;1241&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHtMsy/dJMcacD78rW/z2zXrlRGjgIO5yAom3yCv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHtMsy/dJMcacD78rW/z2zXrlRGjgIO5yAom3yCv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHtMsy/dJMcacD78rW/z2zXrlRGjgIO5yAom3yCv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHtMsy%2FdJMcacD78rW%2Fz2zXrlRGjgIO5yAom3yCv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;824&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;824&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;9. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBNhn1/dJMcacRvKzR/xMhEC9vWBQQGWmMVrGgov1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBNhn1/dJMcacRvKzR/xMhEC9vWBQQGWmMVrGgov1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBNhn1/dJMcacRvKzR/xMhEC9vWBQQGWmMVrGgov1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBNhn1%2FdJMcacRvKzR%2FxMhEC9vWBQQGWmMVrGgov1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;10. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br1Lqb/dJMcahrSVeb/18lRLQsUSWpoUCvssoK900/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br1Lqb/dJMcahrSVeb/18lRLQsUSWpoUCvssoK900/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br1Lqb/dJMcahrSVeb/18lRLQsUSWpoUCvssoK900/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr1Lqb%2FdJMcahrSVeb%2F18lRLQsUSWpoUCvssoK900%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. &amp;lt;template&amp;gt;에&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;입력&amp;nbsp;폼을&amp;nbsp;추가하고&amp;nbsp;이&amp;nbsp;컴포넌트에서만&amp;nbsp;사용하는&amp;nbsp;CSS를&amp;nbsp;&amp;lt;style&amp;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;1299&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JkWRI/dJMcabrBQ34/KHy1jtG6CPvyJREEtTq7uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JkWRI/dJMcabrBQ34/KHy1jtG6CPvyJREEtTq7uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JkWRI/dJMcabrBQ34/KHy1jtG6CPvyJREEtTq7uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJkWRI%2FdJMcabrBQ34%2FKHy1jtG6CPvyJREEtTq7uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;864&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785738751555&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;test&quot;&amp;gt;점심 메뉴 등록&amp;lt;/div&amp;gt;
    &amp;lt;form class=&quot;lunchMenuForm&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;label for=&quot;lunchMenuName&quot;&amp;gt;점심 메뉴 명 :&amp;lt;/label&amp;gt;
            &amp;lt;input type=&quot;text&quot; id=&quot;lunchMenuName&quot; 
                placeholder=&quot;점심 메뉴 명을 입력하세요.&quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;button class=&quot;button blue&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
            &amp;lt;button class=&quot;button&quot;&amp;gt;취소&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
    setup() {
    }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.lunchMenuForm {
    margin: 10px 10px; padding: 10px 10px 0px 10px; background-color: white;
}
.lunchMenuForm &amp;gt; div {
    padding-bottom: 10px; display: flex;
}
.lunchMenuForm label {
    flex: 0 0 120px; line-height: 38px;
}
.lunchMenuForm input[type=&quot;text&quot;] {
    width: 100%; font-size: 14px;
}
button {
    display: inline-block; padding: 0.375rem 0.75rem; min-width: 95px;
    border: 1px solid #6c757d; border-radius: 0.375rem; margin-left: 10px;
    cursor: pointer;
}
button:first-child {
    margin-left: 0;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;style&amp;gt;에서&amp;nbsp;scoped를&amp;nbsp;설정하면&amp;nbsp;컴포넌트에서만&amp;nbsp;적용되는&amp;nbsp;스타일이&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;12.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSodYx/dJMcajiS9ZL/ZqePKIR9ww5xieDTe8bTDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSodYx/dJMcajiS9ZL/ZqePKIR9ww5xieDTe8bTDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSodYx/dJMcajiS9ZL/ZqePKIR9ww5xieDTe8bTDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSodYx%2FdJMcajiS9ZL%2FZqePKIR9ww5xieDTe8bTDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;13. setup()에&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;명이&amp;nbsp;문자열로&amp;nbsp;저장될&amp;nbsp;lunchMenuName이&amp;nbsp;반응형이&amp;nbsp;되도록&amp;nbsp;reactive(리엑티브)&amp;nbsp;함수를&amp;nbsp;사용합니다.&amp;nbsp;그리고&amp;nbsp;&amp;lt;template&amp;gt;에서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;리턴합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785738851988&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { reactive } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: &quot;&quot;
        });

        state.lunchMenuName = &quot;Hello&quot;;

        return {
            state
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이&amp;nbsp;반응형&amp;nbsp;상태가&amp;nbsp;되도록&amp;nbsp;하는&amp;nbsp;ref(레프)&amp;nbsp;함수와&amp;nbsp;다르게&amp;nbsp;reactive(리엑티브)&amp;nbsp;함수는&amp;nbsp;객체&amp;nbsp;자체를&amp;nbsp;반응형으로&amp;nbsp;처리합니다.&amp;nbsp;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/reactivity-fundamentals#reactive&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/reactivity-fundamentals#reactive&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14. &amp;lt;template&amp;gt;에서&amp;nbsp;lunchMenuName과&amp;nbsp;&amp;lt;input&amp;gt;&amp;nbsp;태그가&amp;nbsp;양방향&amp;nbsp;바인딩되게&amp;nbsp;v-model&amp;nbsp;디렉티브를&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785738923202&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;test&quot;&amp;gt;점심 메뉴 등록&amp;lt;/div&amp;gt;
    &amp;lt;form class=&quot;lunchMenuForm&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;label for=&quot;lunchMenuName&quot;&amp;gt;점심 메뉴 명 :&amp;lt;/label&amp;gt;
            &amp;lt;input type=&quot;text&quot; id=&quot;lunchMenuName&quot; 
                placeholder=&quot;점심 메뉴 명을 입력하세요.&quot;
                v-model=&quot;state.lunchMenuName&quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;button class=&quot;button blue&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
            &amp;lt;button class=&quot;button&quot;&amp;gt;취소&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Form&amp;nbsp;입력&amp;nbsp;바인딩에&amp;nbsp;대한&amp;nbsp;설명을&amp;nbsp;참고하세요. &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/forms.html#form-input-bindings&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/forms.html#form-input-bindings&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;15.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdD0EB/dJMcac452uw/g5KFoyTv1LOuhmpGMtwckK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdD0EB/dJMcac452uw/g5KFoyTv1LOuhmpGMtwckK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdD0EB/dJMcac452uw/g5KFoyTv1LOuhmpGMtwckK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdD0EB%2FdJMcac452uw%2Fg5KFoyTv1LOuhmpGMtwckK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;b&gt; toRef vs toRefs &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 객체인 state의 모든 속성에 대해 toRef()로 반환되게 하기 위해서는 toRefs() 함수를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785739044635&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { reactive, toRefs } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: &quot;&quot;
        });

        state.lunchMenuName = &quot;Hello&quot;;

        const { lunchMenuName } = toRefs(state);
        lunchMenuName.value = &quot;Hello World!!&quot;;

        return {
            lunchMenuName
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toRefs()는&amp;nbsp;반응형&amp;nbsp;객체의&amp;nbsp;모든&amp;nbsp;속성에&amp;nbsp;대해&amp;nbsp;ref를&amp;nbsp;만들어&amp;nbsp;리턴합니다. &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/api/reactivity-utilities.html#torefs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/api/reactivity-utilities.html#torefs&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든&amp;nbsp;속성이&amp;nbsp;분해&amp;nbsp;할당됩니다.&amp;nbsp;Destructuring(디스트럭처링,&amp;nbsp;구조&amp;nbsp;분해&amp;nbsp;할당) &lt;/p&gt;
&lt;pre id=&quot;code_1785739150854&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { lunchMenuName } = toRefs(state);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형&amp;nbsp;객체인&amp;nbsp;state의&amp;nbsp;모든&amp;nbsp;속성에&amp;nbsp;대해&amp;nbsp;toRef()로&amp;nbsp;반환되게&amp;nbsp;하기&amp;nbsp;위해서는&amp;nbsp;toRefs()&amp;nbsp;함수를&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785739232193&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { reactive, toRefs } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: &quot;&quot;
        });

        state.lunchMenuName = &quot;Hello World!!&quot;;

        return {
            ...toRefs(state)
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spread Operator(스프레드 오퍼레이터)를 사용하여 모든 속성을 분해합니다. &amp;rarr; 모든 속성들이 ref()로 리턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RUuIf/dJMcaccXEfC/HZKsqq7I3jwb03gxlwXLyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RUuIf/dJMcaccXEfC/HZKsqq7I3jwb03gxlwXLyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RUuIf/dJMcaccXEfC/HZKsqq7I3jwb03gxlwXLyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRUuIf%2FdJMcaccXEfC%2FHZKsqq7I3jwb03gxlwXLyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16. 버튼이&amp;nbsp;클릭되면&amp;nbsp;함수(핸들러)가&amp;nbsp;호출되도록&amp;nbsp;@click(v-on:click)를&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785739324677&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;test&quot;&amp;gt;점심 메뉴 등록&amp;lt;/div&amp;gt;
    &amp;lt;form class=&quot;lunchMenuForm&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;label for=&quot;lunchMenuName&quot;&amp;gt;점심 메뉴 명 :&amp;lt;/label&amp;gt;
            &amp;lt;input type=&quot;text&quot; id=&quot;lunchMenuName&quot; 
                placeholder=&quot;점심 메뉴 명을 입력하세요.&quot;
                v-model=&quot;lunchMenuName&quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
            &amp;lt;button class=&quot;button blue&quot; @click=&quot;addClick&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
            &amp;lt;button class=&quot;button&quot; @click=&quot;cancelClick&quot;&amp;gt;취소&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@는 v-on 디렉티브를 단축한 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@이벤트=함수(핸들러)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수(핸들러)는 setup()에 함수로 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 핸들링에 대한 설명을 참고하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/event-handling.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/event-handling.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-on에 대한 설명을 참고하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://ko.vuejs.org/api/built-in-directives#v-on&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/api/built-in-directives#v-on&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;17. setup()에&amp;nbsp;버튼&amp;nbsp;클릭에&amp;nbsp;대한&amp;nbsp;함수(핸들러)를&amp;nbsp;정의하고&amp;nbsp;리턴합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785739403928&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { reactive, toRefs } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: &quot;&quot;
        });

        function addClick() {
        }

        function cancelClick() {
        }

        return {
            ...toRefs(state),
            addClick,
            cancelClick
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;18. 취소 버튼이 클릭되면 이전 경로로 이동하기 위해서 라우터 인스턴스를 받아서 이전 경로로 이동하도록 router.back() 또는 router.go() 함수를 사용합니다. &amp;rarr; Router 프로그램 방식 &lt;/p&gt;
&lt;pre id=&quot;code_1785739431582&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { reactive, toRefs } from 'vue';
import { useRouter } from 'vue-router';

export default {
    setup() {
        const router = useRouter();
        :
        function cancelClick() {
            router.back();
        }
        :
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router.back();은&amp;nbsp;router.go(-1);과&amp;nbsp;같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 히스토리로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/essentials/navigation.html#Traverse-history&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/essentials/navigation.html#Traverse-history&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;19. 등록&amp;nbsp;버튼이&amp;nbsp;클릭되면&amp;nbsp;axios(엑시오스)의&amp;nbsp;post()&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;APIServer에&amp;nbsp;점심&amp;nbsp;메뉴가&amp;nbsp;추가&amp;nbsp;되도록&amp;nbsp;합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785739515592&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:
import axios from 'axios';

export default {
    setup() {
        :
        const state = reactive({
            lunchMenuName: &quot;&quot;
        }); 

        function addClick() {
            const lunchMenuItem = { menuName : state.lunchMenuName };
            axios.post(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, lunchMenuItem).then((res) =&amp;gt; {
                console.log(res);
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }
        :
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/unzYy/dJMcaaGjdKD/njQRY7JmCTRU2Sy2gIrb2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/unzYy/dJMcaaGjdKD/njQRY7JmCTRU2Sy2gIrb2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/unzYy/dJMcaaGjdKD/njQRY7JmCTRU2Sy2gIrb2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FunzYy%2FdJMcaaGjdKD%2FnjQRY7JmCTRU2Sy2gIrb2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;21. 점심&amp;nbsp;메뉴가&amp;nbsp;추가되면&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;리스트로&amp;nbsp;이동되도록&amp;nbsp;router.replace()&amp;nbsp;함수를&amp;nbsp;사용합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785739571527&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
:

export default {
    setup() {
        const router = useRouter();
        
        const state = reactive({
            lunchMenuName: &quot;&quot;
        }); 

        function addClick() {
            const lunchMenuItem = { menuName : state.lunchMenuName };
            axios.post(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;, lunchMenuItem).then(() =&amp;gt; {
                router.replace({name : 'lunchmenulist'});
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }
        :
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라우터&amp;nbsp;이동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router.push() 함수와 router.replace() 함수는 이동할 위치 정보를 이용하여 vue 컴포넌트로 이동합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;router.push()&amp;nbsp;(&lt;a href=&quot;https://router.vuejs.kr/api/interfaces/Router.html#push&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/api/interfaces/Router.html#push&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;router.replace()&amp;nbsp;(&lt;a href=&quot;https://router.vuejs.kr/api/interfaces/Router.html#replace&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/api/interfaces/Router.html#replace&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router.push()&amp;nbsp;함수를&amp;nbsp;사용하면&amp;nbsp;히스토리에&amp;nbsp;추가됩니다.&amp;nbsp;웹&amp;nbsp;브라우저에서&amp;nbsp;이전&amp;nbsp;버튼을&amp;nbsp;클릭하면&amp;nbsp;현재&amp;nbsp;링크(등록&amp;nbsp;뷰&amp;nbsp;컴포넌트)로&amp;nbsp;이동합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785739653437&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;router.push({name : 'lunchmenulist'});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router.replace() 함수를 사용하면 현재 히스토리가 변경됩니다. 웹 브라우저에서 이전 버튼을 클릭하면 현재 링크(등록 뷰 컴포넌트)의 이전 링크(리스트 뷰 컴포넌트)로 이동합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785739673288&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;router.replace({name : 'lunchmenulist'});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;21. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pMWMh/dJMcacD79mf/Byou7WKOysD4wHPEYqAJzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pMWMh/dJMcacD79mf/Byou7WKOysD4wHPEYqAJzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pMWMh/dJMcacD79mf/Byou7WKOysD4wHPEYqAJzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpMWMh%2FdJMcacD79mf%2FByou7WKOysD4wHPEYqAJzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>named-routes</category>
      <category>nested-routes</category>
      <category>node.js</category>
      <category>reactive</category>
      <category>toref</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/330</guid>
      <comments>https://carrotweb.tistory.com/330#entry330comment</comments>
      <pubDate>Mon, 3 Aug 2026 15:51:05 +0900</pubDate>
    </item>
    <item>
      <title>[5] Backend 개발 - API Server 개발, Node.JS + Search</title>
      <link>https://carrotweb.tistory.com/329</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;검색(Search)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 있는 테이블에서 검색 조건에 맞는 데이터만 검색(select) 하기 위해서는 SELECT 할 때 WHERE 절을 사용하면 됩니다.&lt;/p&gt;
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 타입(검색할 테이블의 컬럼)&lt;/li&gt;
&lt;li&gt;검색 조건(검색하고자 하는 키워드가 테이블의 컬럼 값과 일치하는지 또는 포함되는지에 대한 조건)&lt;/li&gt;
&lt;li&gt;검색&amp;nbsp;키워드(검색할&amp;nbsp;테이블의&amp;nbsp;컬럼에서&amp;nbsp;검색할&amp;nbsp;키워드)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AkHII/dJMcabkUg5r/D5K884UDAUt0yKfSoSANyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AkHII/dJMcabkUg5r/D5K884UDAUt0yKfSoSANyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AkHII/dJMcabkUg5r/D5K884UDAUt0yKfSoSANyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAkHII%2FdJMcabkUg5r%2FD5K884UDAUt0yKfSoSANyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;104&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;153&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;WHERE 절을 MyBatis Mapper XML에서 조건에 따라 처리되게 만들기 위해서는 &amp;lt;where&amp;gt;를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 타입(searchType), 검색 조건(searchCondition), 검색 키워드(searchKeyword)&lt;/p&gt;
&lt;pre id=&quot;code_1785736808950&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;select id=&quot;selectLunchMenuList&quot;&amp;gt;
    SELECT TLM.*
     FROM tb_lunch_menu TLM
    &amp;lt;where&amp;gt;
        &amp;lt;if test='searchKeyword != null and searchKeyword != &quot;&quot;'&amp;gt;
            &amp;lt;choose&amp;gt;
                &amp;lt;when test='searchType != null and searchType == &quot;menuName&quot;'&amp;gt;
                    &amp;lt;choose&amp;gt;
                        &amp;lt;when test='searchCondition != null and searchCondition == &quot;like&quot;'&amp;gt;
                            TLM.menu_name LIKE CONCAT('%',#{searchKeyword},'%')
                        &amp;lt;/when&amp;gt;
                        &amp;lt;otherwise&amp;gt;
                            TLM.menu_name = #{searchKeyword}
                        &amp;lt;/otherwise&amp;gt;
                    &amp;lt;/choose&amp;gt;
                &amp;lt;/when&amp;gt;
            &amp;lt;/choose&amp;gt;
        &amp;lt;/if&amp;gt;
    &amp;lt;/where&amp;gt;
     ORDER BY TLM.REG_DTM DESC
     LIMIT ${pageStartRecordNo}, ${countPerPage}
&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 키워드가 있고 검색 타입이 menuName 이면 검색 조건(포함하면 like, 그렇지 않으면 일치)에 따라 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 키워드가 없거나 검색 타입이 일치하지 않으면 WHERE 절은 생성되지 않습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심 메뉴 리스트(selectLunchMenuList)와 점심 메뉴 리스트 수(selectLunchMenuListCount)에서 동일한 WHERE 절을 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mapper&amp;nbsp;XML에서는&amp;nbsp;동일하게&amp;nbsp;사용되는&amp;nbsp;쿼리문을&amp;nbsp;분리하여&amp;nbsp;재사용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;&amp;lt;sql&amp;gt;를&amp;nbsp;지원하고&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736889590&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;sql id=&quot;whereLunchMenuList&quot;&amp;gt;
    &amp;lt;where&amp;gt;
        &amp;lt;if test='searchKeyword != null and searchKeyword != &quot;&quot;'&amp;gt;
            &amp;lt;choose&amp;gt;
                &amp;lt;when test='searchType != null and searchType == &quot;menuName&quot;'&amp;gt;
                    &amp;lt;choose&amp;gt;
                        &amp;lt;when test='searchCondition != null and searchCondition == &quot;like&quot;'&amp;gt;
                            TLM.menu_name LIKE CONCAT('%',#{searchKeyword},'%')
                        &amp;lt;/when&amp;gt;
                        &amp;lt;otherwise&amp;gt;
                            TLM.menu_name = #{searchKeyword}
                        &amp;lt;/otherwise&amp;gt;
                    &amp;lt;/choose&amp;gt;
                &amp;lt;/when&amp;gt;
            &amp;lt;/choose&amp;gt;
        &amp;lt;/if&amp;gt;
    &amp;lt;/where&amp;gt;
&amp;lt;/sql&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;select&amp;gt;에서 &amp;lt;sql&amp;gt;를 가져오기 위해서 &amp;lt;include&amp;gt;를 추가하고 &amp;lt;include&amp;gt;의 refid 속성에 &amp;lt;sql&amp;gt;의 id을 입력하면 &amp;lt;sql&amp;gt;에 있는 쿼리문이 포함되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;검색(Search)&amp;nbsp;모듈&amp;nbsp;추가&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\db\mappers\mariadb\lunchmenu\lunchmenu-sql.xml 파일을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736955734&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    :

    &amp;lt;sql id=&quot;whereLunchMenuList&quot;&amp;gt;
        &amp;lt;where&amp;gt;
            &amp;lt;if test='searchKeyword != null and searchKeyword != &quot;&quot;'&amp;gt;
                &amp;lt;choose&amp;gt;
                    &amp;lt;when test='searchType != null and searchType == &quot;menuName&quot;'&amp;gt;
                        &amp;lt;choose&amp;gt;
                            &amp;lt;when test='searchCondition != null and searchCondition == &quot;like&quot;'&amp;gt;
                                TLM.menu_name LIKE CONCAT('%',#{searchKeyword},'%')
                            &amp;lt;/when&amp;gt;
                            &amp;lt;otherwise&amp;gt;
                                TLM.menu_name = #{searchKeyword}
                            &amp;lt;/otherwise&amp;gt;
                        &amp;lt;/choose&amp;gt;
                    &amp;lt;/when&amp;gt;
                &amp;lt;/choose&amp;gt;
            &amp;lt;/if&amp;gt;
        &amp;lt;/where&amp;gt;
    &amp;lt;/sql&amp;gt;

    &amp;lt;select id=&quot;selectLunchMenuList&quot;&amp;gt;
        SELECT TLM.*
          FROM tb_lunch_menu TLM
        &amp;lt;include refid=&quot;whereLunchMenuList&quot;/&amp;gt;
         ORDER BY TLM.REG_DTM DESC
         LIMIT ${pageStartRecordNo}, ${countPerPage}
    &amp;lt;/select&amp;gt;
    
    
    &amp;lt;select id=&quot;selectLunchMenuListCount&quot;&amp;gt;
        SELECT count(TLM.seq) AS listCount
          FROM tb_lunch_menu TLM
        &amp;lt;include refid=&quot;whereLunchMenuList&quot;/&amp;gt;
    &amp;lt;/select&amp;gt;

    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\search.js&amp;nbsp;파일을&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785737012075&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 검색
// HTTP 요청 req
const search = function(req) {
    // 검색 타입
    let searchType = req.query.searchtype;
    if (searchType == undefined || typeof searchType == &quot;undefined&quot; || searchType == null) {
        searchType = &quot;&quot;;
    }
    // 검색 조건 - 일치(equal), 포함(like)
    let searchCondition = req.query.searchcondition;
    if (searchCondition == undefined || typeof searchCondition == &quot;undefined&quot; || searchCondition == null) {
        searchCondition = &quot;equal&quot;;
    }
    // 검색 키워드
    let searchKeyword = req.query.searchkeyword;
    if (searchKeyword == undefined || typeof searchKeyword == &quot;undefined&quot; || searchKeyword == null) {
        searchKeyword = &quot;&quot;;
    }

    // 검색 정보
    const searchInfo = {
        searchType : searchType,
        searchCondition : searchCondition,
        searchKeyword : searchKeyword
    }
    return searchInfo;
}

module.exports = search;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\api\v1\lunchmenu.js&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785737050282&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
const search = require('./search');
:

// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method &amp;ndash; Read All Lunch Menu.&quot;);

    const searchInfo = search(req);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: [],
        pagination: {}
    };

    let conn;
    try {
        conn = await getDBConnection();

        const params = {
            searchType : searchInfo.searchType,
            searchCondition : searchInfo.searchCondition,
            searchKeyword : searchInfo.searchKeyword
        };

        //const query = &quot;SELECT count(seq) AS listCount FROM tb_lunch_menu&quot;;
        let query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
            'selectLunchMenuListCount', params, format);

        // 전체 크기
        const dataCount = await conn.query(query);
        if (dataCount.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            // 결과에 숫자 타입(int(n, N), float(f, F), double(d, D), long(l, L))이 같이 옵니다. [ { listCount: 3n } ]
            const totalCount = parseInt(dataCount[0].listCount);

            // 페이지네이션 정보
            // 전체 레코드 수 totalCount
            // 페이지 크기 req.query.countperpage
            // 페이지 번호 req.query.pageno
            // 페이지 사이즈 req.query.pagesize
            const paginationInfo = pagination(totalCount, req.query.pageno, req.query.countperpage, req.query.pagesize);

            if (totalCount &amp;gt; 0) {
                params.pageStartRecordNo = paginationInfo.pageStartRecordNo;
                params.countPerPage = paginationInfo.countPerPage;

                //const query = &quot;SELECT * FROM tb_lunch_menu ORDER BY REG_DTM DESC&quot;;
                query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
                    'selectLunchMenuList', params, format);

                const data = await conn.query(query);
                if (data.length == 0) {
                    result.message = &quot;데이터가 없습니다.&quot;;
                } else {
                    result.message = &quot;데이터가 조회되었습니다.&quot;;
                    result.data = data;
                }
            } else {
                result.message = &quot;데이터가 없습니다.&quot;;
            }

            // 페이지네이션 정보
            result.pagination = paginationInfo;
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심&amp;nbsp;메뉴&amp;nbsp;API&amp;nbsp;테스트&amp;nbsp;(POSTMAN에서&amp;nbsp;확인) &lt;br /&gt;1.&amp;nbsp;전체&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;조회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://127.0.0.1:9000/api/v1/lunchmenus?searchtype=menuName&amp;amp;searchcondition=like&amp;amp;searchkeyword=%EB%B3%B6&lt;br /&gt;%EB%B3%B6 &amp;rarr; 볶&lt;br /&gt;문자열 디코딩(&lt;a href=&quot;https://dencode.com/)&quot;&gt;https://dencode.com/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLZoTu/dJMcaf8wGUK/G3fAEJgb0QNeuahhGr35gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLZoTu/dJMcaf8wGUK/G3fAEJgb0QNeuahhGr35gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLZoTu/dJMcaf8wGUK/G3fAEJgb0QNeuahhGr35gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLZoTu%2FdJMcaf8wGUK%2FG3fAEJgb0QNeuahhGr35gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785737166262&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;result&quot;: &quot;success&quot;,
    &quot;code&quot;: 200,
    &quot;message&quot;: &quot;데이터가 조회되었습니다.&quot;,
    &quot;data&quot;: [
        {
            &quot;seq&quot;: 3,
            &quot;menu_name&quot;: &quot;볶음밥&quot;,
            &quot;reg_dtm&quot;: &quot;2025-01-17 13:37:26&quot;
        }
    ],
    &quot;pagination&quot;: {
        &quot;totalCount&quot;: 1,
        &quot;countPerPage&quot;: 10,
        &quot;pageSize&quot;: 10,
        &quot;startPageNo&quot;: 1,
        &quot;endPageNo&quot;: 1,
        &quot;lastPageNo&quot;: 1,
        &quot;pageNo&quot;: 1,
        &quot;pageStartRecordNo&quot;: 0,
        &quot;pageEndRecordNo&quot;: 0
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>Nosw.JS</category>
      <category>search</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/329</guid>
      <comments>https://carrotweb.tistory.com/329#entry329comment</comments>
      <pubDate>Mon, 3 Aug 2026 15:08:59 +0900</pubDate>
    </item>
    <item>
      <title>[4] Backend 개발 - API Server 개발, Node.JS + Paging</title>
      <link>https://carrotweb.tistory.com/328</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;페이징(Paging),&amp;nbsp;페이지(Page)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 페이징(Paging) 없이 데이터를 가져오면 전체 데이터를 반환하면 서버 부하(데이터베이스 부하, 네트워크 트래픽 부하)가 발생합니다. 그래서 데이터를 가져오기 전에 한 번에 가져올 데이터의 크기를 정해서 가져오게 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;일정한&amp;nbsp;크기로&amp;nbsp;데이터를&amp;nbsp;가져오는&amp;nbsp;것을&amp;nbsp;페이징(Paging)이라고&amp;nbsp;하고&amp;nbsp;일정한&amp;nbsp;크기를&amp;nbsp;가진&amp;nbsp;데이터를&amp;nbsp;페이지(Page)라고&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dd3YfZ/dJMcadJDqnL/nuDVOYCmGeOkWUb8IsR1d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dd3YfZ/dJMcadJDqnL/nuDVOYCmGeOkWUb8IsR1d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dd3YfZ/dJMcadJDqnL/nuDVOYCmGeOkWUb8IsR1d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdd3YfZ%2FdJMcadJDqnL%2FnuDVOYCmGeOkWUb8IsR1d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1459&quot; height=&quot;243&quot; data-origin-width=&quot;1459&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Query String(쿼리 스트링)으로 URL 주소 뒤에 붙여서 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 크기(countperpage) : 가져올 ROW 수&lt;/li&gt;
&lt;li&gt;페이지 번호(pageno) : 가져올 페이지 번호 &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://127.0.0.1:9000/api/v1/lunchmenus?&lt;span style=&quot;color: #ee2323;&quot;&gt;countperpage=10&amp;amp;pageno=1&lt;/span&gt; &lt;br /&gt;1번부터&amp;nbsp;10번까지&amp;nbsp;점심&amp;nbsp;메뉴를&amp;nbsp;가져옵니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://127.0.0.1:9000/api/v1/lunchmenus?&lt;span style=&quot;color: #ee2323;&quot;&gt;countperpage=5&amp;amp;pageno=2&lt;/span&gt; &lt;br /&gt;6번부터&amp;nbsp;10번까지&amp;nbsp;점심&amp;nbsp;메뉴를&amp;nbsp;가져옵니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;페이징(Paging)&amp;nbsp;기능&amp;nbsp;추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\lunchmenu.js 파일을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심 메뉴 조회에 페이지 크기(countperpage), 페이지 번호(pageno)를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y7Rw9/dJMcaixw7HP/k5Uz2bk75f7kjmhVScyyN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y7Rw9/dJMcaixw7HP/k5Uz2bk75f7kjmhVScyyN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y7Rw9/dJMcaixw7HP/k5Uz2bk75f7kjmhVScyyN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy7Rw9%2FdJMcaixw7HP%2Fk5Uz2bk75f7kjmhVScyyN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;826&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785735440455&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    // 페이지 크기
    let countPerPage = req.query.countperpage;
    // 페이지 번호
    let pageNo = req.query.pageno;

    // 페이지 크기가 없으면 10으로 설정
    if (countPerPage == undefined || typeof countPerPage == &quot;undefined&quot; || countPerPage == null) {
        countPerPage = 10;
    } else {
        countPerPage = parseInt(countPerPage);
    }
    // 페이지 번호가 없으면 1로 설정
    if (pageNo == undefined || typeof pageNo == &quot;undefined&quot; || pageNo == null) {
        pageNo = 1;
    } else {
        pageNo = parseInt(pageNo);
    }

    :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\db\mappers\mariadb\lunchmenu\lunchmenu-sql.xml 파일을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 크기(countperpage)와 페이지 번호(pageno)를 사용하여 로우 시작 번호와 끝 번호를 계산하기 위해서 전체 크기를 가져와야 합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785735536539&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;

&amp;lt;mapper namespace=&quot;apiserver.db.mappers.mariadb.lunchmenu&quot;&amp;gt;  
    &amp;lt;select id=&quot;selectLunchMenuList&quot;&amp;gt;
        SELECT *
          FROM tb_lunch_menu
         ORDER BY REG_DTM DESC
    &amp;lt;/select&amp;gt;

    &amp;lt;select id=&quot;selectLunchMenuListCount&quot;&amp;gt;
        SELECT count(seq) AS listCount
          FROM tb_lunch_menu
    &amp;lt;/select&amp;gt;

    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리문에&amp;nbsp;페이징(Paging)을&amp;nbsp;처리하기&amp;nbsp;위해&amp;nbsp;LIMIT를&amp;nbsp;추가합니다.&amp;nbsp;(&lt;a href=&quot;https://mariadb.com/kb/en/limit/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mariadb.com/kb/en/limit/&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1785735586715&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;

&amp;lt;mapper namespace=&quot;apiserver.db.mappers.mariadb.lunchmenu&quot;&amp;gt;  
    &amp;lt;select id=&quot;selectLunchMenuList&quot;&amp;gt;
        SELECT *
          FROM tb_lunch_menu
         ORDER BY REG_DTM DESC
         LIMIT ${pageStartRecordNo}, ${countPerPage}
    &amp;lt;/select&amp;gt;

    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROW&amp;nbsp;시작&amp;nbsp;번호부터&amp;nbsp;가져올&amp;nbsp;ROW&amp;nbsp;수만큼&amp;nbsp;ROW를&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;Oracle에서는&amp;nbsp;ROW_NUMBER를&amp;nbsp;사용하여&amp;nbsp;ROW의&amp;nbsp;번호를&amp;nbsp;부여하고&amp;nbsp;WHERE&amp;nbsp;절에서&amp;nbsp;BETWEEN으로&amp;nbsp;가져옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785735626840&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;select id=&quot;selectLunchMenuList&quot;&amp;gt;
    SELECT   *
              , RNUM
     FROM (
               SELECT   *
                         , ROW_NUMBER() OVER(ORDER BY REG_DTM) AS RNUM
                FROM tb_lunch_menu
              )
    WHERE RNUM BETWEEN ${pageStartRecordNo} AND ${pageEndRecordNo}
&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\api\v1\lunchmenu.js 파일을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 크기(countperpage)와 페이지 번호(pageno)를 사용하여 로우 시작 번호와 끝 번호를 계산합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785735686519&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    :

    let conn;
    try {
        conn = await getDBConnection();

        //const query = &quot;SELECT count(seq) AS listCount FROM tb_lunch_menu&quot;;
        let query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
            'selectLunchMenuListCount', {}, format);

        // 전체 크기
        const dataCount = await conn.query(query);
        if (dataCount.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            // 결과에 숫자 타입(int(n, N), float(f, F), double(d, D), long(l, L))이 같이 옵니다. [ { listCount: 3n } ]
            const totalCount = parseInt(dataCount[0].listCount);

            // 시작 번호
            let pageStartRecordNo = ((pageNo - 1) * countPerPage);
            // 전체 크기가 0이면
            if (totalCount == 0) {
                pageStartRecordNo = 0;
            }
            // 종료 번호
            let pageEndRecordNo = (pageNo * countPerPage) - 1;
            // 전체 크기가 0이면
            if (totalCount == 0) {
                pageEndRecordNo = 0;
            } else if (pageEndRecordNo &amp;gt; (totalCount - 1)) {
                // 종료 번호가 전체 크기보다 크면 전체 크기로 변경
                pageEndRecordNo = totalCount - 1;
            }

            const params = {
                pageStartRecordNo : pageStartRecordNo,
                countPerPage : countPerPage
            };

            //const query = &quot;SELECT * FROM tb_lunch_menu ORDER BY REG_DTM DESC&quot;;
            query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
                'selectLunchMenuList', params, format);

            const data = await conn.query(query);
            if (data.length == 0) {
                result.message = &quot;데이터가 없습니다.&quot;;
            } else {
                result.message = &quot;데이터가 조회되었습니다.&quot;;
                result.data = data;
            }
        }
    }

    :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심 메뉴 API 테스트 (POSTMAN에서 확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 전체 점심 메뉴 조회&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/02aki/dJMcagzEZto/svtDwk0oYgbalnDv6kR7O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/02aki/dJMcagzEZto/svtDwk0oYgbalnDv6kR7O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/02aki/dJMcagzEZto/svtDwk0oYgbalnDv6kR7O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F02aki%2FdJMcagzEZto%2FsvtDwk0oYgbalnDv6kR7O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;페이지네이션(Pagination)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나누어진 Page(페이지)를 이전 페이지나 다음 페이지 또는 특정 페이지로 이동할 수 있게 처리하는 것을 Pagination(페이지네이션)이라고 합니다.&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;1535&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uDeYs/dJMcabdYuIQ/vjFFIJfHYPLyU8VBmWh7w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uDeYs/dJMcabdYuIQ/vjFFIJfHYPLyU8VBmWh7w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uDeYs/dJMcabdYuIQ/vjFFIJfHYPLyU8VBmWh7w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuDeYs%2FdJMcabdYuIQ%2FvjFFIJfHYPLyU8VBmWh7w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;366&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;366&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 사이즈(pageSize)&lt;/li&gt;
&lt;li&gt;전체 마지막 페이지 번호(lastPageNo)&lt;/li&gt;
&lt;li&gt;시작 페이지 번호(startPageNo)&lt;/li&gt;
&lt;li&gt;종료 페이지 번호(endPageNo)&lt;/li&gt;
&lt;li&gt;이전 페이지 번호 = 시작 페이지 번호(startPageNo) - 1&lt;/li&gt;
&lt;li&gt;다음&amp;nbsp;페이지&amp;nbsp;번호&amp;nbsp;=&amp;nbsp;종료&amp;nbsp;페이지&amp;nbsp;번호(endPageNo)&amp;nbsp;+&amp;nbsp;1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;페이지네이션(Pagination)&amp;nbsp;정보&amp;nbsp;추가&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\lunchmenu.js 파일을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 사이즈(pagesize)를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785735951557&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    :
    // 페이지 사이즈
    let pageSize = req.query.pagesize;

    :
    // 페이지 사이즈가 없으면 10으로 설정
    if (pageSize == undefined || typeof pageSize == &quot;undefined&quot; || pageSize == null) {
        pageSize = 10;
    } else {
        pageSize = parseInt(pageSize);
    }

    :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Query&amp;nbsp;String(쿼리&amp;nbsp;스트링)으로&amp;nbsp;URL&amp;nbsp;주소&amp;nbsp;뒤에&amp;nbsp;붙여서&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://127.0.0.1:9000/api/v1/lunchmenus?&lt;span style=&quot;color: #ee2323;&quot;&gt;pagesize=5&lt;/span&gt;&amp;amp;countperpage=5&amp;amp;pageno=3 &lt;br /&gt;11번부터&amp;nbsp;15번까지&amp;nbsp;점심&amp;nbsp;메뉴를&amp;nbsp;가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 마지막 페이지 번호(lastPageNo)를 계산합니다. 전체&amp;nbsp;크기(totalCount)를&amp;nbsp;페이지&amp;nbsp;크기(countPerPage)로&amp;nbsp;나누고&amp;nbsp;나머지가&amp;nbsp;있으면&amp;nbsp;1을&amp;nbsp;더합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736068768&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    :
        // 전체 크기
        const dataCount = await conn.query(query);
        if (dataCount.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            // 결과에 숫자 타입(int(n, N), float(f, F), double(d, D), long(l, L))이 같이 옵니다. [ { listCount: 3n } ]
            const totalCount = parseInt(dataCount[0].listCount);

            // 전체 마지막 페이지 번호
            const lastPageNo = Math.floor(totalCount / countPerPage) + (totalCount % countPerPage == 0 ? 0 : 1);

    :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 페이지 번호(startPageNo)를 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지&amp;nbsp;사이즈(pageSize)에서&amp;nbsp;페이지&amp;nbsp;번호(pageNo)를&amp;nbsp;나누고&amp;nbsp;나머지가&amp;nbsp;없으면&amp;nbsp;1을&amp;nbsp;뺍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736181750&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    :

            // 결과에 숫자 타입(int(n, N), float(f, F), double(d, D), long(l, L))이 같이 옵니다. [ { listCount: 3n } ]
            const totalCount = parseInt(dataCount[0].listCount);

            // 전체 마지막 페이지 번호
            const lastPageNo = Math.floor(totalCount / countPerPage) + (totalCount % countPerPage == 0 ? 0 : 1);
            // 시작 페이지 번호
            let startPageNo = 1;
            // 페이지 사이즈로 페이지 번호를 나눈 몫만큼 페이지 시작 번호 변경
            let start = Math.floor(pageNo / pageSize);
            if (start &amp;gt;= 1) {
                // 그렇지만 나머지가 없으면 현재 페이지 번호가 마지막 페이지 번호와 같아 감소
                if (pageNo % pageSize == 0) {
                    start--;
                }
                startPageNo = (start * pageSize) + 1;
            }
    :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지&amp;nbsp;번호가&amp;nbsp;10이면&amp;nbsp;start가&amp;nbsp;1이&amp;nbsp;됩니다.&amp;nbsp;그러면&amp;nbsp;시작&amp;nbsp;페이지&amp;nbsp;번호가&amp;nbsp;11이&amp;nbsp;됩니다.&amp;nbsp;그래서&amp;nbsp;페이지&amp;nbsp;사이즈의&amp;nbsp;배수(즉&amp;nbsp;나머지가&amp;nbsp;없으면)이면&amp;nbsp;start에서&amp;nbsp;1을&amp;nbsp;뺍니다.&amp;nbsp;그러면&amp;nbsp;시작&amp;nbsp;페이지&amp;nbsp;번호가&amp;nbsp;1이&amp;nbsp;됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료 페이지 번호(endPageNo)를 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작&amp;nbsp;페이지&amp;nbsp;번호(startPageNo)에서&amp;nbsp;1을&amp;nbsp;빼고&amp;nbsp;페이지&amp;nbsp;사이즈(pageSize)를&amp;nbsp;더하면&amp;nbsp;됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736217458&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    :

            // 시작 페이지 번호
            let startPageNo = 1;
            :
            // 종료 페이지 번호
            let endPageNo = (startPageNo - 1) + pageSize;
            // 그렇지만 종료 페이지 번호가 마지막 페이지 번호보다 크면 마지막 페이지 번호로 변경
            if (endPageNo &amp;gt; lastPageNo) {
                endPageNo = lastPageNo;
            }
    :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지네이션(endPageNo)을&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736277759&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    :

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: [],
        pagination: {}
    };

            :

            // 페이지네이션 정보
            result.pagination.totalCount = totalCount;
            result.pagination.countPerPage = countPerPage;
            result.pagination.pageSize = pageSize;
            result.pagination.startPageNo = startPageNo;
            result.pagination.endPageNo = endPageNo;
            result.pagination.lastPageNo = lastPageNo;
            result.pagination.pageNo = pageNo;
            :
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심 메뉴 API 테스트 (POSTMAN에서 확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;전체&amp;nbsp;점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LqXYE/dJMcadv4qHD/vsy6to3kUmWPV40Vg3Y1G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LqXYE/dJMcadv4qHD/vsy6to3kUmWPV40Vg3Y1G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LqXYE/dJMcadv4qHD/vsy6to3kUmWPV40Vg3Y1G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLqXYE%2FdJMcadv4qHD%2Fvsy6to3kUmWPV40Vg3Y1G1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785736332218&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;result&quot;: &quot;success&quot;,
    &quot;code&quot;: 200,
    &quot;message&quot;: &quot;데이터가 조회되었습니다.&quot;,
    &quot;data&quot;: [
        {
            &quot;seq&quot;: 1,
            &quot;menu_name&quot;: &quot;짜장면&quot;,
            &quot;reg_dtm&quot;: &quot;2025-01-17 13:37:00&quot;
        }
    ],
    &quot;pagination&quot;: {
        &quot;totalCount&quot;: 3,
        &quot;countPerPage&quot;: 2,
        &quot;pageSize&quot;: 10,
        &quot;startPageNo&quot;: 1,
        &quot;endPageNo&quot;: 2,
        &quot;lastPageNo&quot;: 2,
        &quot;pageNo&quot;: 2
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;페이지네이션(Pagination)&amp;nbsp;모듈로&amp;nbsp;처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\pagination.js&amp;nbsp;파일을&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785736451043&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 페이지네이션
// 전체 레코드 수 totalCount
// 페이지 번호 pageNo
// 페이지 크기 countPerPage
// 페이지 사이즈 pageSize
const pagination = function(totalCount, pageNo, countPerPage, pageSize) {
    // 페이지 번호가 없으면 1로 설정
    if (pageNo == undefined || typeof pageNo == &quot;undefined&quot; || pageNo == null) {
        pageNo = 1;
    } else {
        pageNo = parseInt(pageNo);
    }
    // 페이지 크기가 없으면 10으로 설정
    if (countPerPage == undefined || typeof countPerPage == &quot;undefined&quot; || countPerPage == null) {
        countPerPage = 10;
    } else {
        countPerPage = parseInt(countPerPage);
    }
    // 페이지 사이즈가 없으면 10으로 설정
    if (pageSize == undefined || typeof pageSize == &quot;undefined&quot; || pageSize == null) {
        pageSize = 10;
    } else {
        pageSize = parseInt(pageSize);
    }

    // 전체 마지막 페이지 번호
    const lastPageNo = Math.floor(totalCount / countPerPage) + (totalCount % countPerPage == 0 ? 0 : 1);

    // 시작 페이지 번호
    let startPageNo = 1;
    // 페이지 사이즈로 페이지 번호를 나눈 몫만큼 페이지 시작 번호 변경
    let start = Math.floor(pageNo / pageSize);
    if (start &amp;gt;= 1) {
        // 그렇지만 나머지가 없으면 현재 페이지 번호가 마지막 페이지 번호와 같아 감소
        if (pageNo % pageSize == 0) {
            start--;
        }
        startPageNo = (start * pageSize) + 1;
    }

    // 종료 페이지 번호
    let endPageNo = (startPageNo - 1) + pageSize;
    // 그렇지만 종료 페이지 번호가 마지막 페이지 번호보다 크면 마지막 페이지 번호로 변경
    if (endPageNo &amp;gt; lastPageNo) {
         endPageNo = lastPageNo;
    }

    // 시작 번호
    let pageStartRecordNo = ((pageNo - 1) * countPerPage);
    // 전체 크기가 0이면
    if (totalCount == 0) {
        pageStartRecordNo = 0;
    }
    
    // 종료 번호
    let pageEndRecordNo = (pageNo * countPerPage) - 1;
    // 전체 크기가 0이면
    if (totalCount == 0) {
        pageEndRecordNo = 0;
    } else if (pageEndRecordNo &amp;gt; (totalCount - 1)) {
        // 종료 번호가 전체 크기보다 크면 전체 크기로 변경
         pageEndRecordNo = totalCount - 1;
    }

    // 페이지네이션 정보
    const paginationInfo = {};
    paginationInfo.totalCount = totalCount;
    paginationInfo.countPerPage = countPerPage;
    paginationInfo.pageSize = pageSize;
    paginationInfo.startPageNo = startPageNo;
    paginationInfo.endPageNo = endPageNo;
    paginationInfo.lastPageNo = lastPageNo;
    paginationInfo.pageNo = pageNo;
    paginationInfo.pageStartRecordNo = pageStartRecordNo;
    paginationInfo.pageEndRecordNo = pageEndRecordNo;
    
    return paginationInfo;
}

module.exports = pagination;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\api\v1\lunchmenu.js&amp;nbsp;파일을&amp;nbsp;수정합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785736511133&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
const pagination = require('./pagination');
:

// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: [],
        pagination: {}
    };

    let conn;
    try {
        conn = await getDBConnection();

        //const query = &quot;SELECT count(seq) AS listCount FROM tb_lunch_menu&quot;;
        let query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
            'selectLunchMenuListCount', {}, format);

        // 전체 크기
        const dataCount = await conn.query(query);
        if (dataCount.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            // 결과에 숫자 타입(int(n, N), float(f, F), double(d, D), long(l, L))이 같이 옵니다. [ { listCount: 3n } ]
            const totalCount = parseInt(dataCount[0].listCount);

            // 페이지네이션 정보
            // 전체 레코드 수 totalCount
            // 페이지 크기 req.query.countperpage
            // 페이지 번호 req.query.pageno
            // 페이지 사이즈 req.query.pagesize
            const paginationInfo = pagination(totalCount, req.query.pageno, req.query.countperpage, req.query.pagesize);

            if (totalCount &amp;gt; 0) {
                const params = {
                    pageStartRecordNo : paginationInfo.pageStartRecordNo,
                    countPerPage : paginationInfo.countPerPage
                };

                //const query = &quot;SELECT * FROM tb_lunch_menu ORDER BY REG_DTM DESC&quot;;
                query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
                    'selectLunchMenuList', params, format);
                console.log(query);
                const data = await conn.query(query);
                if (data.length == 0) {
                    result.message = &quot;데이터가 없습니다.&quot;;
                } else {
                    result.message = &quot;데이터가 조회되었습니다.&quot;;
                    result.data = data;
                }
            } else {
                result.message = &quot;데이터가 없습니다.&quot;;
            }

            // 페이지네이션 정보
            result.pagination = paginationInfo;
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>node.js</category>
      <category>pagination</category>
      <category>paging</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/328</guid>
      <comments>https://carrotweb.tistory.com/328#entry328comment</comments>
      <pubDate>Mon, 3 Aug 2026 14:59:35 +0900</pubDate>
    </item>
    <item>
      <title>[3] Backend 개발 - API Server 개발, Node.JS + MyBatis3</title>
      <link>https://carrotweb.tistory.com/327</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Node.JS에서 MyBatis3 Mapper XML 파일을 이용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;951&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yugRb/dJMcagl6IeZ/LohV4vdvXFrTTImKK711K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yugRb/dJMcagl6IeZ/LohV4vdvXFrTTImKK711K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yugRb/dJMcagl6IeZ/LohV4vdvXFrTTImKK711K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyugRb%2FdJMcagl6IeZ%2FLohV4vdvXFrTTImKK711K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;951&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;951&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Mybatis&amp;nbsp;Mapper&amp;nbsp;설치하기&amp;nbsp;(터미널에서&amp;nbsp;실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785733780267&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install mybatis-mapper --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n2eca/dJMcadQpWxQ/MXHX8dZKPbg6BTWz6Rhux0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n2eca/dJMcadQpWxQ/MXHX8dZKPbg6BTWz6Rhux0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n2eca/dJMcadQpWxQ/MXHX8dZKPbg6BTWz6Rhux0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn2eca%2FdJMcadQpWxQ%2FMXHX8dZKPbg6BTWz6Rhux0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;824&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\db에&amp;nbsp;mappers\mariadb\lunchmenu&amp;nbsp;디렉터리를&amp;nbsp;생성하고&amp;nbsp;lunchmenu-sql.xml&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/waivr/dJMcaiqHkc5/OY6tdw1p71gD1lKF2huPoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/waivr/dJMcaiqHkc5/OY6tdw1p71gD1lKF2huPoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/waivr/dJMcaiqHkc5/OY6tdw1p71gD1lKF2huPoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwaivr%2FdJMcaiqHkc5%2FOY6tdw1p71gD1lKF2huPoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;824&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;824&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;XML은&amp;nbsp;MyBatis3&amp;nbsp;구문과&amp;nbsp;동일합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785733942284&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;

&amp;lt;mapper namespace=&quot;&quot;&amp;gt;
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;mapper&amp;gt;의 namespace(네임스페이스) 속성은 Mapper XML 파일들을 서로 구분하기 위해서 사용됩니다. 그래서 하나의 Mapper XML 파일만 가리켜야 하기 때문에 중복되지 않아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 namespace(네임스페이스)를 프로젝트 명과 XML 파일이 있는 경로를 조합하여 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 명 : apiserver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML&amp;nbsp;파일이&amp;nbsp;있는&amp;nbsp;경로&amp;nbsp;:&amp;nbsp;db\mappers\mariadb\lunchmenu &lt;br /&gt;그래서 mapper namespace는 apiserver.db.mappers.mariadb.lunchmenu입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785734007888&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;

&amp;lt;mapper namespace=&quot;apiserver.db.mappers.mariadb.lunchmenu&quot;&amp;gt;  
    &amp;lt;select id=&quot;&quot;&amp;gt;
    &amp;lt;/select&amp;gt;

    &amp;lt;insert id=&quot;&quot;&amp;gt;
    &amp;lt;/insert&amp;gt;

    &amp;lt;update id=&quot;&quot;&amp;gt;
    &amp;lt;/update&amp;gt;

    &amp;lt;delete id=&quot;&quot;&amp;gt;
    &amp;lt;/delete&amp;gt;
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;mapper&amp;gt; 안에서는 &amp;lt;select&amp;gt;(SELECT 쿼리문), &amp;lt;insert&amp;gt;(INSERT 쿼리문), &amp;lt;update&amp;gt;(UPDATE 쿼리문), &amp;lt;delete&amp;gt;(DELEETE 쿼리문)를 설정할 수 있습니다.&lt;/p&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;lt;select&amp;gt;, &amp;lt;insert&amp;gt;, &amp;lt;update&amp;gt;, &amp;lt;delete&amp;gt;의 id 속성 값은 식별자로 중복되지 않는 이름으로 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id&amp;nbsp;속성은&amp;nbsp;mybatisMapper.getStatement()&amp;nbsp;함수에서&amp;nbsp;구문을&amp;nbsp;찾을&amp;nbsp;때&amp;nbsp;사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id&amp;nbsp;속성&amp;nbsp;값에&amp;nbsp;대한&amp;nbsp;접두사(Prefix)와&amp;nbsp;접미사(suffix)를&amp;nbsp;이용하여&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;명명&amp;nbsp;규칙(NamingRule)을&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;DB : Insert,&amp;nbsp;&amp;nbsp;Select, Update, Delete&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT 쿼리문으로 여러 로우를 가져오면 select&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 로우 수를 가져오면 select&amp;lt;모델명&amp;gt;ListCount&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 한 로우만 가져오면 select&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 하나의 값만 가져오면 select&amp;lt;모델명&amp;gt;Value&lt;/li&gt;
&lt;li&gt;INSERT 쿼리문은 insert&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 수정하면 update&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우를 수정하면 update&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 한 로우만 삭제하면 delete&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 여러 로우를 삭제하면 delete&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRUD : Create,&amp;nbsp;&amp;nbsp;Read, Update, Delete (CRUDL : Create,&amp;nbsp;&amp;nbsp;Read, Update, Delete, List)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT 쿼리문으로 여러 로우를 가져오면 read&amp;lt;모델명&amp;gt;List (list&amp;lt;모델명&amp;gt;)&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 로우 수를 가져오면 read&amp;lt;모델명&amp;gt;ListCount (list&amp;lt;모델명&amp;gt;Count)&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 한 로우만 가져오면 read&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 하나의 값만 가져오면 read&amp;lt;모델명&amp;gt;Value&lt;/li&gt;
&lt;li&gt;INSERT 쿼리문은 create&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 수정하면 update&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우를 수정하면 update&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 한 로우만 삭제하면 delete&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;DELEETE&amp;nbsp;쿼리문으로&amp;nbsp;여러&amp;nbsp;로우를&amp;nbsp;삭제하면&amp;nbsp;delete&amp;lt;모델명&amp;gt;List &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ABCD : Add, Browse, Change, Delete&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT 쿼리문으로 여러 로우를 가져오면 browse&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 로우 수를 가져오면 browse&amp;lt;모델명&amp;gt;Count&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 한 로우만 가져오면 browse&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 하나의 값만 가져오면 browse&amp;lt;모델명&amp;gt;Value&lt;/li&gt;
&lt;li&gt;INSERT 쿼리문은 add&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 수정하면 change&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우를 수정하면 change&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 한 로우만 삭제하면 delete&amp;lt;모델명&amp;gt;Item&lt;/li&gt;
&lt;li&gt;DELEETE&amp;nbsp;쿼리문으로&amp;nbsp;여러&amp;nbsp;로우를&amp;nbsp;삭제하면&amp;nbsp;delete&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BREAD : Browse, Read, Edit, Add, Delete&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT 쿼리문으로 여러 로우를 가져오면 browse&amp;lt;모델명&amp;gt;&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 로우 수를 가져오면 browse&amp;lt;모델명&amp;gt;Count&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 한 로우만 가져오면 read&amp;lt;모델명&amp;gt;&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 하나의 값만 가져오면 read&amp;lt;모델명&amp;gt;Value&lt;/li&gt;
&lt;li&gt;INSERT 쿼리문은 add&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 수정하면 edit&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우를 수정하면 edit&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 한 로우만 삭제하면 delete&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;DELEETE&amp;nbsp;쿼리문으로&amp;nbsp;여러&amp;nbsp;로우를&amp;nbsp;삭제하면&amp;nbsp;delete&amp;lt;모델명&amp;gt;List &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAVE : Delete, Add, View, Edit&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT 쿼리문으로 여러 로우를 가져오면 view&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 로우 수를 가져오면 view&amp;lt;모델명&amp;gt;Count&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 한 로우만 가져오면 view&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 하나의 값만 가져오면 view&amp;lt;모델명&amp;gt;Value&lt;/li&gt;
&lt;li&gt;INSERT 쿼리문은 add&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 수정하면 edit&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우를 수정하면 edit&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 한 로우만 삭제하면 delete&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;DELEETE&amp;nbsp;쿼리문으로&amp;nbsp;여러&amp;nbsp;로우를&amp;nbsp;삭제하면&amp;nbsp;delete&amp;lt;모델명&amp;gt;List &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GRADE : Get, Remove, Add, Delete, Edit&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT 쿼리문으로 여러 로우를 가져오면 get&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 로우 수를 가져오면 get&amp;lt;모델명&amp;gt;ListCount&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 한 로우만 가져오면 get&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;SELECT 쿼리문으로 하나의 값만 가져오면 get&amp;lt;모델명&amp;gt;Value&lt;/li&gt;
&lt;li&gt;INSERT 쿼리문은 add&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 수정하면 edit&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우를 수정하면 edit&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 한 로우만 사용(검색)되지 않게 수정하면 remove&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;UPDATE 쿼리문으로 여러 로우가 사용(검색)되지 않게 수정하면 remove&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;li&gt;DELEETE 쿼리문으로 한 로우만 삭제하면 delete&amp;lt;모델명&amp;gt;Object&lt;/li&gt;
&lt;li&gt;DELEETE&amp;nbsp;쿼리문으로&amp;nbsp;여러&amp;nbsp;로우를&amp;nbsp;삭제하면&amp;nbsp;delete&amp;lt;모델명&amp;gt;List&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;DB 명명 규칙으로 사용하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. lunchmenu-sql.xml&amp;nbsp;파일에&amp;nbsp;src\api\v1\lunchmenu.js&amp;nbsp;파일에&amp;nbsp;있는&amp;nbsp;query를&amp;nbsp;&amp;lt;select&amp;gt;,&amp;nbsp;&amp;lt;insert&amp;gt;,&amp;nbsp;&amp;lt;update&amp;gt;,&amp;nbsp;&amp;lt;delete&amp;gt;&amp;nbsp;구문으로&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;1299&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6kZIV/dJMcajwqXfd/HTQzdIY6DYq0941XGwmN5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6kZIV/dJMcajwqXfd/HTQzdIY6DYq0941XGwmN5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6kZIV/dJMcajwqXfd/HTQzdIY6DYq0941XGwmN5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6kZIV%2FdJMcajwqXfd%2FHTQzdIY6DYq0941XGwmN5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;824&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785734553063&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;

&amp;lt;mapper namespace=&quot;apiserver.db.mappers.mariadb.lunchmenu&quot;&amp;gt;  
    &amp;lt;select id=&quot;selectLunchMenuList&quot;&amp;gt;
        SELECT *
          FROM tb_lunch_menu
         ORDER BY REG_DTM DESC
    &amp;lt;/select&amp;gt;

    &amp;lt;select id=&quot;selectLunchMenuItem&quot;&amp;gt;
        SELECT *
          FROM tb_lunch_menu
         WHERE seq = ${seq}
    &amp;lt;/select&amp;gt;

    &amp;lt;insert id=&quot;insertLunchMenuItem&quot;&amp;gt;
        INSERT INTO tb_lunch_menu
            (
                menu_name
            )
            VALUES
            (
                #{menuName}
            )
    &amp;lt;/insert&amp;gt;

    &amp;lt;update id=&quot;updateLunchMenuItem&quot;&amp;gt;
        UPDATE tb_lunch_menu
           SET menu_name = #{menuName}
         WHERE seq = ${seq}
    &amp;lt;/update&amp;gt;

    &amp;lt;delete id=&quot;deleteLunchMenuItem&quot;&amp;gt;
        DELETE
          FROM tb_lunch_menu
         WHERE seq = ${seq}
    &amp;lt;/delete&amp;gt;
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 문에 있는 #{}, ${}는 전달되는 변수가 들어갈 곳입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#{}은 변수에 들어 갈 값이 따옴표로 묶여 변환됩니다. &amp;rarr; 문자열에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;${}은 변수 그대로 변환됩니다. &amp;rarr; 숫자에 사용&lt;/p&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;API&amp;nbsp;수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\api\v1\lunchmenu.js&amp;nbsp;파일을&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785734858221&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express  = require('express');
const router = express.Router();
const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/lunchmenu-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createMapper([XML Files])&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[XML Files]은 XML 파일 경로들이 있는 배열입니다.&lt;/li&gt;
&lt;li&gt;XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 ＇./＇ 또는 ＇/＇ 없이 입력해야 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Root에 있다면 ./test.xml, test.xml&lt;/li&gt;
&lt;li&gt;Root의 하위 디렉터리인 src에 있다면 ./src/test.xml, src/test.xml&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;지정된 xml 파일을 읽고 구문 분석하여 SQL 문을 생성할 준비를 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;format&amp;nbsp;변수의&amp;nbsp;language&amp;nbsp;옵션 &lt;br /&gt;(&lt;a href=&quot;https://github.com/sql-formatter-org/sql-formatter/blob/HEAD/docs/language.md#options&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sql-formatter-org/sql-formatter/blob/HEAD/docs/language.md#options&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pF5Qt/dJMcagTZ94p/rMkgKzKYQzo7AOhJBnoUvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pF5Qt/dJMcagTZ94p/rMkgKzKYQzo7AOhJBnoUvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pF5Qt/dJMcagTZ94p/rMkgKzKYQzo7AOhJBnoUvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpF5Qt%2FdJMcagTZ94p%2FrMkgKzKYQzo7AOhJBnoUvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;837&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&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;pre id=&quot;code_1785735112242&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    :

    //const query = &quot;SELECT * FROM tb_lunch_menu ORDER BY REG_DTM DESC&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'selectLunchMenuList', {}, format);

    :
        data = await conn.query(query);
    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getStatement(namespace: string, sql: string, param?: mybatisMapper.Params, format?: mybatisMapper.Format)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;namespace은 네임스페이스로 XML 파일에 있는 &amp;lt;mapper&amp;gt;의&amp;nbsp;&amp;nbsp;namespace 속성 값입니다.&lt;/li&gt;
&lt;li&gt;sql는 XML 파일에 있는 &amp;lt;select&amp;gt;, &amp;lt;insert&amp;gt;, &amp;lt;update&amp;gt;, &amp;lt;delete&amp;gt;의 id 속성 값입니다.&lt;/li&gt;
&lt;li&gt;Parameters는 XML 파일에 있는 SQL 문에 있는 #{}, ${} 변수에 들어갈 값입니다.&lt;/li&gt;
&lt;li&gt;format은 옵션입니다. SQL 언어와 들여쓰기를 설정할 수 있습니다. (&lt;a href=&quot;https://www.npmjs.com/package/sql-formatter)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/sql-formatter&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785735167817&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 점심 메뉴 번호로 검색하여 점심 메뉴를 리턴합니다.
router.get('/:no', async function(req, res, next) {
    :

    const params = {
        seq : req.params.no
    };

    //const query = &quot;SELECT * FROM tb_lunch_menu WHERE seq = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'selectLunchMenuItem', params, format);

    :
        data = await conn.query(query);
    :
});

// 점심 메뉴를 등록합니다.
router.post('/', async function(req, res, next) {
    :

    const params = {
        menuName : req.body.menuName
    };

    //const query = &quot;INSERT INTO tb_lunch_menu (menu_name) VALUES(?)&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'insertLunchMenuItem', params, format);

    :
         data = await conn.query(query);
    :
});

// 점심 메뉴를 수정합니다.
router.put('/:no', async function(req, res, next) {
    :

    const params = {
        menuName : req.body.menuName,
        seq : req.params.no
    };

    //const query = &quot;UPDATE tb_lunch_menu SET menu_name = ? WHERE seq = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'updateLunchMenuItem', params, format);

    :
        data = await conn.query(query);
    :
});

// 점심 메뉴를 삭제합니다.
router.delete('/:no', async function(req, res, next) {
    :

    const params = {
        seq : req.params.no
    };

    //const query = &quot;DELETE FROM tb_lunch_menu WHERE seq = ?&quot;;
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu', 
        'deleteLunchMenuItem', params, format);

    :
         data = await conn.query(query);
    :
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>MyBatis3</category>
      <category>namingRule</category>
      <category>node.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/327</guid>
      <comments>https://carrotweb.tistory.com/327#entry327comment</comments>
      <pubDate>Mon, 3 Aug 2026 14:36:13 +0900</pubDate>
    </item>
    <item>
      <title>[2] Frontend 개발</title>
      <link>https://carrotweb.tistory.com/326</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;개발&amp;nbsp;환경을&amp;nbsp;구성하기&amp;nbsp;위해&amp;nbsp;프로그램&amp;nbsp;설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project Scaffolding(프로젝트 스캐폴딩)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 시작하기 위한 구조와 설정 등을 자동으로 처리해주는 툴입니다. &amp;rarr; 애플리케이션의 구조 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가&amp;nbsp;수동으로&amp;nbsp;설치하고&amp;nbsp;설정해야&amp;nbsp;하는&amp;nbsp;것들을&amp;nbsp;프로그램이&amp;nbsp;대신&amp;nbsp;처리해줌으로써&amp;nbsp;개발을&amp;nbsp;빠르게&amp;nbsp;시작할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vue CLI(Command line interface)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue.js 개발을 위한 표준 도구 (빠른 Vue.js 애플리케이션 개발을 위한 시스템을 제공) (&lt;a href=&quot;https://cli.vuejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cli.vuejs.org/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yna5R/dJMcaaGiGJ2/0LhahfILMSABeVtOnOKaK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yna5R/dJMcaaGiGJ2/0LhahfILMSABeVtOnOKaK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yna5R/dJMcaaGiGJ2/0LhahfILMSABeVtOnOKaK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYna5R%2FdJMcaaGiGJ2%2F0LhahfILMSABeVtOnOKaK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;IDE(Integrated&amp;nbsp;Development&amp;nbsp;Environment,&amp;nbsp;통합&amp;nbsp;개발&amp;nbsp;환경)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;VS Code + Vue-Official(extension)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법&amp;nbsp;강조&amp;nbsp;표시,&amp;nbsp;TypeScript&amp;nbsp;지원,&amp;nbsp;템플릿&amp;nbsp;표현식&amp;nbsp;및&amp;nbsp;컴포넌트&amp;nbsp;props에&amp;nbsp;대한&amp;nbsp;인텔리센스&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;1299&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AmJ3O/dJMcagTZHWh/iJG2MKV67P5vxnqwcNquVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AmJ3O/dJMcagTZHWh/iJG2MKV67P5vxnqwcNquVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AmJ3O/dJMcagTZHWh/iJG2MKV67P5vxnqwcNquVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAmJ3O%2FdJMcagTZHWh%2FiJG2MKV67P5vxnqwcNquVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;853&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;853&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;Browser Devtools&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vue.js&amp;nbsp;devtools&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue로&amp;nbsp;만든&amp;nbsp;웹의&amp;nbsp;구조를&amp;nbsp;파악하거나&amp;nbsp;데이터&amp;nbsp;파악하고&amp;nbsp;디버깅을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있게&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;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvUkNN/dJMcaa7mEmX/00CCNueOq9hrZ9k4uFVuWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvUkNN/dJMcaa7mEmX/00CCNueOq9hrZ9k4uFVuWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvUkNN/dJMcaa7mEmX/00CCNueOq9hrZ9k4uFVuWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvUkNN%2FdJMcaa7mEmX%2F00CCNueOq9hrZ9k4uFVuWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&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;&lt;b&gt;Vue&amp;nbsp;CLI로&amp;nbsp;Vue&amp;nbsp;프로젝트&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Vue CLI 설치 (콘솔에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785666262328&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g @vue/cli&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rdshd/dJMb998mga7/GMU5dkMrInj6PxT7N0EKW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rdshd/dJMb998mga7/GMU5dkMrInj6PxT7N0EKW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rdshd/dJMb998mga7/GMU5dkMrInj6PxT7N0EKW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frdshd%2FdJMb998mga7%2FGMU5dkMrInj6PxT7N0EKW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785666308873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6uYop/dJMcacD7BvI/dQKqOI7LjhvZ27njzV6LWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6uYop/dJMcacD7BvI/dQKqOI7LjhvZ27njzV6LWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6uYop/dJMcacD7BvI/dQKqOI7LjhvZ27njzV6LWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6uYop%2FdJMcacD7BvI%2FdQKqOI7LjhvZ27njzV6LWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Vue CLI로 Vue 프로젝트 생성 (콘솔에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785666346822&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue create lunchapp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mzVHJ/dJMcabE5oXO/WmEA9a5fizyTZ4pPh9GUMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mzVHJ/dJMcabE5oXO/WmEA9a5fizyTZ4pPh9GUMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mzVHJ/dJMcabE5oXO/WmEA9a5fizyTZ4pPh9GUMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmzVHJ%2FdJMcabE5oXO%2FWmEA9a5fizyTZ4pPh9GUMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dazWLv/dJMcahk7MKt/E9MLCiNcxTvqURlt3vlF61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dazWLv/dJMcahk7MKt/E9MLCiNcxTvqURlt3vlF61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dazWLv/dJMcahk7MKt/E9MLCiNcxTvqURlt3vlF61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdazWLv%2FdJMcahk7MKt%2FE9MLCiNcxTvqURlt3vlF61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue&amp;nbsp;프로젝트를&amp;nbsp;어떻게&amp;nbsp;구성할&amp;nbsp;건지&amp;nbsp;preset(프리셋&amp;nbsp;:&amp;nbsp;사전&amp;nbsp;설정)합니다.&amp;nbsp;&amp;ndash;&amp;nbsp;Default&amp;nbsp;(Vue&amp;nbsp;3)&amp;nbsp;선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;babel&amp;nbsp;(바벨,&amp;nbsp;&lt;a href=&quot;https://babeljs.io)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://babeljs.io&lt;/a&gt;) : javascript를 이전 버전과 호환되게 코드를 변환해주는 컴파일러 &amp;rarr; 트랜스파일러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eslint&amp;nbsp;(린트/린터,&amp;nbsp;&lt;a href=&quot;https://eslint.org)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://eslint.org&lt;/a&gt;) : javascript 코드에서 발견되는 문제 패턴들을 식별해주는 정적 분석 도구, 코드 규칙을 이용하 코드 스타일 유지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RmjcO/dJMcac45wgB/D5SmLPVKzeQQe4rmwFiuv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RmjcO/dJMcac45wgB/D5SmLPVKzeQQe4rmwFiuv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RmjcO/dJMcac45wgB/D5SmLPVKzeQQe4rmwFiuv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRmjcO%2FdJMcac45wgB%2FD5SmLPVKzeQQe4rmwFiuv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&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;3. Vue 프로젝트 실행 (콘솔에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785666489888&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd lunchapp; npm run serve&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYmOmQ/dJMcajpvOnC/YUoLr2Czqt860xJ9s0HmU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYmOmQ/dJMcajpvOnC/YUoLr2Czqt860xJ9s0HmU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYmOmQ/dJMcajpvOnC/YUoLr2Czqt860xJ9s0HmU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYmOmQ%2FdJMcajpvOnC%2FYUoLr2Czqt860xJ9s0HmU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 웹 브라우저에서 확인 (http://localhost:8080/)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nE9nk/dJMcaiYsyEv/FIrk7jGrDsNSvdfOH6kKMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nE9nk/dJMcaiYsyEv/FIrk7jGrDsNSvdfOH6kKMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nE9nk/dJMcaiYsyEv/FIrk7jGrDsNSvdfOH6kKMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnE9nk%2FdJMcaiYsyEv%2FFIrk7jGrDsNSvdfOH6kKMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;5. 웹&amp;nbsp;경로와&amp;nbsp;Vue&amp;nbsp;컴포넌트를&amp;nbsp;연결하기&amp;nbsp;위해&amp;nbsp;Vue&amp;nbsp;Router&amp;nbsp;설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router &amp;ndash;웹 경로와 Vue 컴포넌트를 연결해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://router.vuejs.kr&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCXFuY/dJMcaaM006v/kc00LmghQZOZiklD1n5ETK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCXFuY/dJMcaaM006v/kc00LmghQZOZiklD1n5ETK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCXFuY/dJMcaaM006v/kc00LmghQZOZiklD1n5ETK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCXFuY%2FdJMcaaM006v%2Fkc00LmghQZOZiklD1n5ETK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&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;Vue Router Plugin으로 vue-router를 설치하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue에서는&amp;nbsp;vue&amp;nbsp;router에&amp;nbsp;대한&amp;nbsp;일련의&amp;nbsp;작업들을&amp;nbsp;자동으로&amp;nbsp;처리해&amp;nbsp;Plugin을&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;Vue Router Plugin 설치&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(콘솔에서 실행합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785667294444&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue add router&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqOUfx/dJMcajb03nd/OZ8jk4WIQ8B8EroLTpdAK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqOUfx/dJMcajb03nd/OZ8jk4WIQ8B8EroLTpdAK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqOUfx/dJMcajb03nd/OZ8jk4WIQ8B8EroLTpdAK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqOUfx%2FdJMcajb03nd%2FOZ8jk4WIQ8B8EroLTpdAK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가&amp;nbsp;진행되는&amp;nbsp;중에&amp;nbsp;&quot;Use&amp;nbsp;history&amp;nbsp;mode&amp;nbsp;for&amp;nbsp;router?&amp;nbsp;(Requires&amp;nbsp;proper&amp;nbsp;server&amp;nbsp;setup&amp;nbsp;for&amp;nbsp;index&amp;nbsp;fallback&amp;nbsp;in&amp;nbsp;production)&amp;nbsp;-&amp;nbsp;라우터에&amp;nbsp;히스토리&amp;nbsp;모드를&amp;nbsp;사용&amp;nbsp;하시겠습니까?&amp;nbsp;(프로덕션에서&amp;nbsp;인덱스&amp;nbsp;폴백을&amp;nbsp;위한&amp;nbsp;적절한&amp;nbsp;서버&amp;nbsp;설정&amp;nbsp;필요합니다.)&quot;라는&amp;nbsp;컨펌&amp;nbsp;메시지가&amp;nbsp;나타납니다.&amp;nbsp;라우터를&amp;nbsp;히스토리&amp;nbsp;모드로&amp;nbsp;사용하기&amp;nbsp;위해&amp;nbsp;&quot;Y&quot;을&amp;nbsp;입력하고&amp;nbsp;엔터키를&amp;nbsp;누릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue&amp;nbsp;Router&amp;nbsp;디렉터리&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;1520&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PVIFu/dJMcaidgYEc/OgdGst6gDGimJb07gnlg2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PVIFu/dJMcaidgYEc/OgdGst6gDGimJb07gnlg2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PVIFu/dJMcaidgYEc/OgdGst6gDGimJb07gnlg2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPVIFu%2FdJMcaidgYEc%2FOgdGst6gDGimJb07gnlg2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1520&quot; height=&quot;837&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;837&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;1524&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1WvC/dJMb99UPnxp/KJFXKpgRtHsqfBJxftjLK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1WvC/dJMb99UPnxp/KJFXKpgRtHsqfBJxftjLK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1WvC/dJMb99UPnxp/KJFXKpgRtHsqfBJxftjLK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1WvC%2FdJMb99UPnxp%2FKJFXKpgRtHsqfBJxftjLK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;864&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. index.js에 Router(라우터) 인스턴스를 생성&lt;/p&gt;
&lt;pre id=&quot;code_1785667715358&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    component: () =&amp;gt; import(/* webpackChunkName: &quot;about&quot; */ '../views/AboutView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터&amp;nbsp;인스턴스&amp;nbsp;생성 &lt;br /&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/#Registering-the-router-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/#Registering-the-router-plugin&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wzzp1/dJMcaccW86D/qfw6A8gNz4xBGanp8kw7Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wzzp1/dJMcaccW86D/qfw6A8gNz4xBGanp8kw7Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wzzp1/dJMcaccW86D/qfw6A8gNz4xBGanp8kw7Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWzzp1%2FdJMcaccW86D%2Fqfw6A8gNz4xBGanp8kw7Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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. App.vue에 Router(라우터) 인스턴스를 use() 함수로 등록&lt;/p&gt;
&lt;pre id=&quot;code_1785667963342&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;use()&amp;nbsp;함수는&amp;nbsp;mount()&amp;nbsp;함수&amp;nbsp;이전에&amp;nbsp;실행되어야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터&amp;nbsp;인스턴스&amp;nbsp;등록 &lt;br /&gt;(&lt;a href=&quot;https://router.vuejs.kr/guide/#Registering-the-router-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://router.vuejs.kr/guide/#Registering-the-router-plugin&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddlyOT/dJMcagsTCNJ/KpPVinUiDbne08XwA0kb9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddlyOT/dJMcagsTCNJ/KpPVinUiDbne08XwA0kb9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddlyOT/dJMcagsTCNJ/KpPVinUiDbne08XwA0kb9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddlyOT%2FdJMcagsTCNJ%2FKpPVinUiDbne08XwA0kb9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;8. 웹 브라우저에서 확인 (http://localhost:8080/)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/43W3f/dJMcajwqvRk/p1VzD6tRk4OwtkJPhhMbNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/43W3f/dJMcajwqvRk/p1VzD6tRk4OwtkJPhhMbNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/43W3f/dJMcajwqvRk/p1VzD6tRk4OwtkJPhhMbNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F43W3f%2FdJMcajwqvRk%2Fp1VzD6tRk4OwtkJPhhMbNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;9. API&amp;nbsp;서버와&amp;nbsp;연동하기&amp;nbsp;위해&amp;nbsp;Vue&amp;nbsp;Axios(액시오스)&amp;nbsp;설치&lt;/p&gt;
&lt;pre id=&quot;code_1785668057963&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install axios --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCZYjV/dJMcahSNCoN/YRzGecKq67OT1kruRCVE00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCZYjV/dJMcahSNCoN/YRzGecKq67OT1kruRCVE00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCZYjV/dJMcahSNCoN/YRzGecKq67OT1kruRCVE00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCZYjV%2FdJMcahSNCoN%2FYRzGecKq67OT1kruRCVE00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;740&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;740&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;&lt;b&gt;Options&amp;nbsp;API&amp;nbsp;vs&amp;nbsp;Composition&amp;nbsp;API&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;1375&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmpkaq/dJMcaaM01t2/dwdArmqkClb5nAn7FrPcD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmpkaq/dJMcaaM01t2/dwdArmqkClb5nAn7FrPcD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmpkaq/dJMcaaM01t2/dwdArmqkClb5nAn7FrPcD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmpkaq%2FdJMcaaM01t2%2FdwdArmqkClb5nAn7FrPcD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1375&quot; height=&quot;599&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;599&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;Vue&amp;nbsp;2&amp;nbsp;지원은&amp;nbsp;2023년&amp;nbsp;12월&amp;nbsp;31일에&amp;nbsp;종료 &lt;br /&gt;API 스타일에 대한 설명을 참고하세요. (&lt;a href=&quot;https://ko.vuejs.org/guide/introduction.html#api-styles&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/introduction.html#api-styles&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;LunchMenuListView&amp;nbsp;컴포넌트&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. src\views에서&amp;nbsp;LunchMenu&amp;nbsp;디렉터리&amp;nbsp;생성,&amp;nbsp;LunchMenuListView.vue&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eADbOE/dJMcafAMhwY/KgVLvkBMQ4IU1UWcaq23F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eADbOE/dJMcafAMhwY/KgVLvkBMQ4IU1UWcaq23F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eADbOE/dJMcafAMhwY/KgVLvkBMQ4IU1UWcaq23F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeADbOE%2FdJMcafAMhwY%2FKgVLvkBMQ4IU1UWcaq23F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;853&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785668252996&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src\router\index.js에서&amp;nbsp;LunchMenuListView.vue를&amp;nbsp;import하고&amp;nbsp;routes에&amp;nbsp;route를&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;1299&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/51Lom/dJMcabdX5h0/HUo0qPtRO7C8Vjem3QmFxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/51Lom/dJMcabdX5h0/HUo0qPtRO7C8Vjem3QmFxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/51Lom/dJMcabdX5h0/HUo0qPtRO7C8Vjem3QmFxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F51Lom%2FdJMcabdX5h0%2FHUo0qPtRO7C8Vjem3QmFxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;854&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785668289716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:
import LunchMenuListView from '@/views/LunchMenu/LunchMenuListView.vue'

const routes = [
  :
  {
    path: '/lunchmenu/',
    name: 'lunchmenu',
    component: LunchMenuListView
  }
]

:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@는&amp;nbsp;단축&amp;nbsp;경로로&amp;nbsp;&quot;src/*&quot;&amp;nbsp;경로를&amp;nbsp;단축한&amp;nbsp;겁니다. &lt;br /&gt;vue.config.js&amp;nbsp;파일에&amp;nbsp;정의되어&amp;nbsp;있습니다. &lt;br /&gt;&amp;nbsp;&quot;paths&quot;:&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@/*&quot;:&amp;nbsp;[&quot;src/*&quot;] &lt;br /&gt;&amp;nbsp;},&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. src\App.vue에서&amp;nbsp;router-link를&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;1299&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JvjMr/dJMcacYldXv/YqSj5M5dgbOjdBNKtPk0z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JvjMr/dJMcacYldXv/YqSj5M5dgbOjdBNKtPk0z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JvjMr/dJMcacYldXv/YqSj5M5dgbOjdBNKtPk0z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJvjMr%2FdJMcacYldXv%2FYqSj5M5dgbOjdBNKtPk0z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;854&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;854&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;Router 선언적 방식 (v3.x)&lt;/p&gt;
&lt;pre id=&quot;code_1785668405600&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;nav&amp;gt;
    &amp;lt;router-link to=&quot;/&quot;&amp;gt;Home&amp;lt;/router-link&amp;gt; |
    &amp;lt;router-link to=&quot;/about&quot;&amp;gt;About&amp;lt;/router-link&amp;gt; |
    &amp;lt;router-link to=&quot;/lunchmenu/&quot;&amp;gt;LunchMenu&amp;lt;/router-link&amp;gt;
  &amp;lt;/nav&amp;gt;
  &amp;lt;router-view/&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크를 생성하기 위해서 &amp;lt;a&amp;gt;태그가 아닌 router-link 컴포넌트를 사용합니다. &amp;rarr; router-link 컴포넌트는 &amp;lt;a&amp;gt;태그로 렌더링 됩니다. (&amp;lt;router-link to=&quot;&quot;&amp;gt; &amp;rarr; &amp;lt;a href=&quot;&quot;&amp;gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router-view 컴포넌트는 현재 컴포넌트에서 렌더링할 위치입니다. &amp;rarr; 현재 경로에 맞는 컴포넌트가 렌더링 됩니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(v4.x)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785668504506&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;nav&amp;gt;
    &amp;lt;RouterLink to=&quot;/&quot;&amp;gt;Home&amp;lt;/RouterLink&amp;gt; |
    &amp;lt;RouterLink to=&quot;/about&quot;&amp;gt;About&amp;lt;/RouterLink&amp;gt; |
    &amp;lt;RouterLink to=&quot;/lunchmenu/&quot;&amp;gt;LunchMenu&amp;lt;/RouterLink&amp;gt;
  &amp;lt;/nav&amp;gt;
  &amp;lt;RouterView/&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 웹&amp;nbsp;브라우저에서&amp;nbsp;확인&amp;nbsp;(http://localhost:8080/lunchmenu)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yMGY6/dJMcaazuSkm/pgcIdwkQLiVyKcXNtbibwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yMGY6/dJMcaazuSkm/pgcIdwkQLiVyKcXNtbibwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yMGY6/dJMcaazuSkm/pgcIdwkQLiVyKcXNtbibwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyMGY6%2FdJMcaazuSkm%2FpgcIdwkQLiVyKcXNtbibwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;&lt;b&gt;LunchMenuListView&amp;nbsp;컴포넌트&amp;nbsp;개발&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. setup()에&amp;nbsp;점심&amp;nbsp;메뉴들이&amp;nbsp;리스트로&amp;nbsp;저장될&amp;nbsp;lunchMenuList를&amp;nbsp;선언하고&amp;nbsp;값이&amp;nbsp;반응형&amp;nbsp;상태가&amp;nbsp;되도록&amp;nbsp;ref(레프,&amp;nbsp;reference)&amp;nbsp;함수를&amp;nbsp;사용합니다.&amp;nbsp;그리고&amp;nbsp;&amp;lt;template&amp;gt;에서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;리턴합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785668607069&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref } from 'vue';

export default {
    name : 'LunchMenuListView',
    setup() {
        let lunchMenuList = ref([]);

        return {
            lunchMenuList
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let lunchMenuList = []; &amp;larr; 값이 변동되므로 let로 변수 선언&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let lunchMenuList = ref([]); &amp;larr; 값에 ref() 함수를 적용하여 반응형 상태가 되도록 함 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API에서는 반응형 상태를 선언하는 ref() 함수입니다. &amp;rarr; 반응성 &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/reactivity-fundamentals#ref&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/reactivity-fundamentals#ref&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s1Jom/dJMcajwqv6s/MksnTXU0MHLurwEhSMUkQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s1Jom/dJMcajwqv6s/MksnTXU0MHLurwEhSMUkQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s1Jom/dJMcajwqv6s/MksnTXU0MHLurwEhSMUkQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs1Jom%2FdJMcajwqv6s%2FMksnTXU0MHLurwEhSMUkQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &amp;lt;template&amp;gt;에서&amp;nbsp;lunchMenuList가&amp;nbsp;있는지&amp;nbsp;없는지에&amp;nbsp;따라&amp;nbsp;블록을&amp;nbsp;렌더링&amp;nbsp;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785668693590&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
    &amp;lt;div v-if=&quot;lunchMenuList.length == 0&quot;&amp;gt;
        점심 메뉴가 없습니다.
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-else&amp;gt;
        점심 메뉴가 {{ lunchMenuList.length }} 개 있습니다.
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Directive(디렉티브) : 지시문으로 &amp;lt;template&amp;gt;에서 v- 접두사가 붙어 사용되는 속성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-if는 조건이 true일 때 블록을 렌더링 합니다. &amp;rarr; 조건부 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-else는 v-if에 대한 else 블록입니다.&lt;/p&gt;
&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;{{ }}(Mustache, 머스테치, 이중 괄호)는 데이터를 DOM에 바인딩합니다. &amp;rarr; 보간(Interpolation)법(중간에 무언가를 끼워 넣다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건부로&amp;nbsp;블록을&amp;nbsp;렌더링합니다. &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/conditional.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/conditional.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lrLo4/dJMcahk7NMQ/agK7zl17GGxn6XzsSsvsP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lrLo4/dJMcahk7NMQ/agK7zl17GGxn6XzsSsvsP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lrLo4/dJMcahk7NMQ/agK7zl17GGxn6XzsSsvsP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlrLo4%2FdJMcahk7NMQ%2FagK7zl17GGxn6XzsSsvsP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/template-syntax#text-interpolation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/template-syntax#text-interpolation&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hfk5b/dJMcabrBnRB/FwsMKH8XBiC05z44UYE7h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hfk5b/dJMcabrBnRB/FwsMKH8XBiC05z44UYE7h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hfk5b/dJMcabrBnRB/FwsMKH8XBiC05z44UYE7h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHfk5b%2FdJMcabrBnRB%2FFwsMKH8XBiC05z44UYE7h0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;3. axios의&amp;nbsp;get()&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;APIServer에서&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;리스트를&amp;nbsp;받아서&amp;nbsp;lunchMenuList에&amp;nbsp;적용하는&amp;nbsp;getLunchMenuList()&amp;nbsp;함수를&amp;nbsp;생성하고&amp;nbsp;컴포넌트에서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;리턴합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785668807863&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { ref } from 'vue';
import axios from 'axios';

export default {
    name : 'LunchMenuListView',
    setup() {
        let lunchMenuList = ref([]);

        function getLunchMenuList() {
            axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;).then((res) =&amp;gt; {
                lunchMenuList.value = res.data.data;
            }).catch((err) =&amp;gt; {
                console.log(err);
            });
        }

        return {
            lunchMenuList, getLunchMenuList
        }
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup()안에서 ref() 함수로 작용된 값에 접근할 때는 value로 속성을 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref()는 반응형 ref 객체를 반환합니다. Ref 객체 안에는 단일 속성으로 value가 있습니다. value 속성에 값이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios의 get() 함수는 HTTP GET 메서드로 요청합니다. &amp;larr; 비동기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;then() 메서드는 요청(HTTP Request)에 대한 응답(HTTP Response)이 성공하면 들어오는 메서드로 callback function를 호출합니다. 파라미터는 응답 객체입니다. &quot;response&quot;이나 &quot;res&quot;로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;catch() 메서드는 요청(HTTP Request)에 대한 응답(HTTP Response)이 실패하면 들어오는 메서드로 callback function를 호출합니다. 파라미터는 에러 객체입니다. &quot;error&quot;이나 &quot;err&quot;로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;then() 메서드와 catch() 메서드에서 function를 직접 사용해서 처리하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;then(function(res) {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;})&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 화살표 함수(arrow function)를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;then((res) =&amp;gt; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;})&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 처리&lt;/p&gt;
&lt;pre id=&quot;code_1785668927556&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getLunchMenuList() {
    axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;).then((res) =&amp;gt; {
        lunchMenuList.value = res.data.data;
    }).catch((err) =&amp;gt; {
        console.log(err);
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기&amp;nbsp;처리&lt;/p&gt;
&lt;pre id=&quot;code_1785668938122&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function getLunchMenuList() {
    try {
        const result = await axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;);
        lunchMenuList.value = result.data.data;
    } catch(err) {
        console.log(err);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;)의&amp;nbsp;결과&amp;nbsp;값 &lt;/p&gt;
&lt;pre id=&quot;code_1785669011340&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;data&quot;: {
        &quot;result&quot;: &quot;success&quot;,
        &quot;code&quot;: 200,
        &quot;message&quot;: &quot;데이터가 조회되었습니다.&quot;,
        &quot;data&quot;: [
            {
                &quot;seq&quot;: 3,
                &quot;menu_name&quot;: &quot;볶음밥&quot;,
                &quot;reg_dtm&quot;: &quot;2025-01-17 13:37:26&quot;
            },
            {
                &quot;seq&quot;: 2,
                &quot;menu_name&quot;: &quot;짬봉&quot;,
                &quot;reg_dtm&quot;: &quot;2025-01-17 13:37:14&quot;
            },
            {
                &quot;seq&quot;: 1,
                &quot;menu_name&quot;: &quot;자장면&quot;,
                &quot;reg_dtm&quot;: &quot;2025-01-17 13:37:00&quot;
            }
        ]
    },
    &quot;status&quot;: 200,
    &quot;statusText&quot;: &quot;OK&quot;,
    &quot;headers&quot;: {
        &quot;content-length&quot;: &quot;283&quot;,
        &quot;content-type&quot;: &quot;application/json; charset=utf-8&quot;
    },
    &quot;config&quot;: {
        &quot;transitional&quot;: {
        &quot;transitional&quot;: {
            &quot;silentJSONParsing&quot;: true,
            &quot;forcedJSONParsing&quot;: true,
            &quot;clarifyTimeoutError&quot;: false
        },
        &quot;adapter&quot;: [
            &quot;xhr&quot;,
            &quot;http&quot;,
            &quot;fetch&quot;
        ],
        &quot;transformRequest&quot;: [
            null
        ],
        &quot;transformResponse&quot;: [
            null
        ],
        &quot;timeout&quot;: 0,
        &quot;xsrfCookieName&quot;: &quot;XSRF-TOKEN&quot;,
        &quot;xsrfHeaderName&quot;: &quot;X-XSRF-TOKEN&quot;,
        &quot;maxContentLength&quot;: -1,
        &quot;maxBodyLength&quot;: -1,
        &quot;env&quot;: {},
        &quot;headers&quot;: {
            &quot;Accept&quot;: &quot;application/json, text/plain, */*&quot;
        },
        &quot;method&quot;: &quot;get&quot;,
        &quot;url&quot;: &quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;
    },
    &quot;request&quot;: {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 컴포넌트가&amp;nbsp;로딩이&amp;nbsp;완료된&amp;nbsp;후&amp;nbsp;점심&amp;nbsp;메뉴&amp;nbsp;리스트를&amp;nbsp;가져오는&amp;nbsp;함수를&amp;nbsp;호출하기&amp;nbsp;위해&amp;nbsp;&amp;lt;script&amp;gt;에&amp;nbsp;mounted()&amp;nbsp;함수를&amp;nbsp;추가하고&amp;nbsp;setup에&amp;nbsp;있는&amp;nbsp;getLunchMenuList()&amp;nbsp;함수가&amp;nbsp;실행되게&amp;nbsp;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785669069345&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { ref } from 'vue';
import axios from 'axios';

export default {
    setup() {
        :
    },
    mounted() {
        this.getLunchMenuList();
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XXH6C/dJMcad3XeqS/sr6kOo8j5OWDurxEleIPHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XXH6C/dJMcad3XeqS/sr6kOo8j5OWDurxEleIPHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XXH6C/dJMcad3XeqS/sr6kOo8j5OWDurxEleIPHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXXH6C%2FdJMcad3XeqS%2Fsr6kOo8j5OWDurxEleIPHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;240&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 마운트 되면 mounted() 훅이 실행됩니다. &amp;rarr; 생명주기 훅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup()에서 리턴 되는 변수는 this(컴포넌트 인스턴스)에 노출됩니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생명주기&amp;nbsp;훅&amp;nbsp;등록&amp;nbsp;&amp;ndash;&amp;nbsp;mounted&amp;nbsp;등록 &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/lifecycle#registering-lifecycle-hooks&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/lifecycle#registering-lifecycle-hooks&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NUFC3/dJMcabrBn4r/dZxnVthvrKrXle3ukVUSK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NUFC3/dJMcabrBn4r/dZxnVthvrKrXle3ukVUSK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NUFC3/dJMcabrBn4r/dZxnVthvrKrXle3ukVUSK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNUFC3%2FdJMcabrBn4r%2FdZxnVthvrKrXle3ukVUSK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;Composition&amp;nbsp;API&amp;nbsp;(컴포지션&amp;nbsp;API)에서&amp;nbsp;setup()&amp;nbsp;함수를&amp;nbsp;&amp;lt;script&amp;nbsp;setup&amp;gt;으로&amp;nbsp;단순화&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785669394656&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, onMounted } from 'vue';
import axios from 'axios';

let lunchMenuList = ref([]);

function getLunchMenuList() {
    axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;).then((res) =&amp;gt; {
        console.log(res);
        lunchMenuList.value = res.data.data;
    }).catch((err) =&amp;gt; {
        console.log(err);
    });
}

onMounted(()=&amp;gt;{
    getLunchMenuList();
});
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup() 밖에서 사용된 mounted를 setup()에서 사용할 수 있습니다. &amp;rarr; onMounted() &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/api/composition-api-lifecycle#onmounted&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/api/composition-api-lifecycle#onmounted&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Options&amp;nbsp;API&amp;nbsp;(옵션&amp;nbsp;API)로&amp;nbsp;작성할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785669433671&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import axios from 'axios';

export default {
    name : 'LunchMenuListView',
    data() {
        return {
            lunchMenuList : []
        }
    },
    methods : {
        async getLunchMenuList() {
            try {
                const result = await axios.get(&quot;http://127.0.0.1:9000/api/v1/lunchmenus/&quot;);
                console.log(result);
                this.lunchMenuList = result.data.data;
            } catch(err) {
                console.log(err);
            }
        }
    },
    mounted() {
        this.getLunchMenuList();
    }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup()에서&amp;nbsp;사용한&amp;nbsp;변수와&amp;nbsp;함수는&amp;nbsp;Options&amp;nbsp;API에서는&amp;nbsp;data와&amp;nbsp;methods로&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;5. lunchMenuList&amp;nbsp;수&amp;nbsp;만큼&amp;nbsp;반복하여&amp;nbsp;&amp;lt;li&amp;gt;가&amp;nbsp;생성되게&amp;nbsp;합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785669167538&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;점심 메뉴 리스트&amp;lt;/div&amp;gt;
    &amp;lt;div v-if=&quot;lunchMenuList.length == 0&quot;&amp;gt;
        점심 메뉴가 없습니다.
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-else&amp;gt;
        점심 메뉴가 {{ lunchMenuList.length }} 개 있습니다.
        &amp;lt;ul class=&quot;menu-list&quot;&amp;gt;
            &amp;lt;li class=&quot;menu-item&quot; v-for=&quot;(lunchMenuItem, index) in lunchMenuList&quot; :key=&quot;index&quot;&amp;gt;
                {{ lunchMenuItem.menu_name }}
            &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-for는 배열을 기반으로 블록을 렌더링 합니다. &amp;rarr; 리스트 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue가 각 노드의 ID를 추적하고 기존 엘리먼트를 재사용하고 재정렬할 수 있도록 힌트를 제공하려면 각 항목에 대해 특수 속성 key를 고유한 값으로 제공해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:key는&amp;nbsp;v-bind:key를&amp;nbsp;단축한&amp;nbsp;겁니다.&amp;nbsp;key는&amp;nbsp;속성입니다.&amp;nbsp;v-bind는&amp;nbsp;속성&amp;nbsp;값에&amp;nbsp;변수를&amp;nbsp;바인딩해줍니다.&amp;nbsp;&amp;nbsp;속성&amp;nbsp;바인딩​&amp;nbsp;(속성에서는&amp;nbsp;이중&amp;nbsp;괄호를&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;없습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을&amp;nbsp;리스트로&amp;nbsp;렌더링합니다.&amp;nbsp; &lt;br /&gt;(&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/list&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/list&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RKaiG/dJMcacxhs5b/DaDBdq3sVJHgINKRPuK2QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RKaiG/dJMcacxhs5b/DaDBdq3sVJHgINKRPuK2QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RKaiG/dJMcacxhs5b/DaDBdq3sVJHgINKRPuK2QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRKaiG%2FdJMcacxhs5b%2FDaDBdq3sVJHgINKRPuK2QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 스타일을&amp;nbsp;적용하기&amp;nbsp;위하여&amp;nbsp;&amp;lt;style&amp;gt;에&amp;nbsp;CSS를&amp;nbsp;추가합니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785669231190&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt;
.menu-list {
    width: 500px;
    margin: 10px auto;
    padding: 0;
}
.menu-item {
    list-style: none;
    padding: 10px 10px;
    background-color: #0a4171;
    color: white;
    margin: 4px 0px;
    cursor: pointer;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 웹&amp;nbsp;브라우저에서&amp;nbsp;확인&amp;nbsp;(http://localhost:8080/lunchmenu)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKmWPx/dJMcafHA0CP/gu7tz9QqpfiXMtiypEltqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKmWPx/dJMcafHA0CP/gu7tz9QqpfiXMtiypEltqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKmWPx/dJMcafHA0CP/gu7tz9QqpfiXMtiypEltqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKmWPx%2FdJMcafHA0CP%2Fgu7tz9QqpfiXMtiypEltqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;837&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/326</guid>
      <comments>https://carrotweb.tistory.com/326#entry326comment</comments>
      <pubDate>Sun, 2 Aug 2026 20:19:34 +0900</pubDate>
    </item>
    <item>
      <title>[1] Backend 개발 - API Server 개발</title>
      <link>https://carrotweb.tistory.com/325</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;APIServer&amp;nbsp;프로젝트&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. toyprojects &amp;gt; apiserver 디렉터리 생성 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785663402251&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir toyprojects; cd toyprojects; mkdir apiserver; cd apiserver; code .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3fIAh/dJMcacqziuT/b2qaXEi9QqcQRLPLruZT61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3fIAh/dJMcacqziuT/b2qaXEi9QqcQRLPLruZT61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3fIAh/dJMcacqziuT/b2qaXEi9QqcQRLPLruZT61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3fIAh%2FdJMcacqziuT%2Fb2qaXEi9QqcQRLPLruZT61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 패키지 초기화 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(&lt;/span&gt;터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785663461404&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOMjdw/dJMcahL5w42/1iP5fCS5KJWE2SnhRD4vkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOMjdw/dJMcahL5w42/1iP5fCS5KJWE2SnhRD4vkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOMjdw/dJMcahL5w42/1iP5fCS5KJWE2SnhRD4vkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOMjdw%2FdJMcahL5w42%2F1iP5fCS5KJWE2SnhRD4vkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Express.js 설치하기 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785663627494&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install express --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVKIW/dJMcabSC6OW/uHYyOngZCMUUo2vUGnOOW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVKIW/dJMcabSC6OW/uHYyOngZCMUUo2vUGnOOW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVKIW/dJMcabSC6OW/uHYyOngZCMUUo2vUGnOOW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVKIW%2FdJMcabSC6OW%2FuHYyOngZCMUUo2vUGnOOW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json에서 dependencies 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGzXcT/dJMcacqziLB/8pgOzaDKIdvvbnQ2Qd4Stk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGzXcT/dJMcacqziLB/8pgOzaDKIdvvbnQ2Qd4Stk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGzXcT/dJMcacqziLB/8pgOzaDKIdvvbnQ2Qd4Stk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGzXcT%2FdJMcacqziLB%2F8pgOzaDKIdvvbnQ2Qd4Stk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Entry Point 파일 생성하기 (index.js 파일 생성, express를 가져와 서버를 생성)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vNCjt/dJMcagl6jyz/mr7FagPnz0KeGsUmPDp6G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vNCjt/dJMcagl6jyz/mr7FagPnz0KeGsUmPDp6G0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vNCjt/dJMcagl6jyz/mr7FagPnz0KeGsUmPDp6G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvNCjt%2FdJMcagl6jyz%2Fmr7FagPnz0KeGsUmPDp6G0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785663981934&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

app.listen(port, () =&amp;gt; {
    console.log(`APIServer started on port ${port}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트를 9000으로 설정하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express Middleware&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://expressjs.com/ko/guide/writing-middleware.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://expressjs.com/ko/guide/writing-middleware.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eB6Mlk/dJMcaa0vKXS/pKXcRjHt6PxZP0DvQpvHmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eB6Mlk/dJMcaa0vKXS/pKXcRjHt6PxZP0DvQpvHmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eB6Mlk/dJMcaa0vKXS/pKXcRjHt6PxZP0DvQpvHmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeB6Mlk%2FdJMcaa0vKXS%2FpKXcRjHt6PxZP0DvQpvHmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;847&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;847&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;Express에서 HTTP 요청과 HTTP 응답 사이에서 수행되는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Middleware는 HTTP 요청이 들어오는 순간부터 순차적으로 실행되고 다음 Middleware가 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(다음 Middleware은 next라는 이름의 변수로 표시됩니다. 현재 처리되는 Middleware에서 next()를 호출하지 않으면 다음 Middleware로 전달되지 않고 중지됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application-level middleware (애플리케이션 레벨 미들웨어)&lt;/p&gt;
&lt;pre id=&quot;code_1785663961453&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express')
const app = express()

const myLogger = function (req, res, next) { &amp;lt;--함수형 미들웨어
  console.log('LOGGED')
  next()
}

app.use(myLogger) &amp;lt;-- 1

app.get('/', (req, res, next) =&amp;gt; { &amp;lt;-- 2
  res.send('Hello World!')
})

app.listen(3000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. npm으로 실행하기 위해 Script 추가 (package.json 파일을 오픈하여 &quot;scripts&quot;에 node 실행문을 추가)&lt;/p&gt;
&lt;pre id=&quot;code_1785664153864&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;start&quot;: &quot;node index.js&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YuQBP/dJMcabdX3wl/AlgzNvy7pxmlsN4KB5PsT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YuQBP/dJMcabdX3wl/AlgzNvy7pxmlsN4KB5PsT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YuQBP/dJMcabdX3wl/AlgzNvy7pxmlsN4KB5PsT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYuQBP%2FdJMcabdX3wl%2FAlgzNvy7pxmlsN4KB5PsT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. npm으로 실행 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785664207526&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHmWuV/dJMcabZvdkn/xuTjcNIZkdweCntWHJe4fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHmWuV/dJMcabZvdkn/xuTjcNIZkdweCntWHJe4fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHmWuV/dJMcabZvdkn/xuTjcNIZkdweCntWHJe4fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHmWuV%2FdJMcabZvdkn%2FxuTjcNIZkdweCntWHJe4fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 웹 브라우저에서 확인 (http://127.0.0.1:9000)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vfE3U/dJMcajpvNo0/U5NJ3yu9iODQ8nnTsQQaTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vfE3U/dJMcajpvNo0/U5NJ3yu9iODQ8nnTsQQaTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vfE3U/dJMcajpvNo0/U5NJ3yu9iODQ8nnTsQQaTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvfE3U%2FdJMcajpvNo0%2FU5NJ3yu9iODQ8nnTsQQaTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;&lt;b&gt;Mariadb Database, Table 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 생성 쿼리 실행&lt;/p&gt;
&lt;pre id=&quot;code_1785664431817&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- lunchdb Database가 있으면 삭제
DROP DATABASE IF EXISTS lunchdb;

-- lunchdb Database가 없으면 생성
CREATE DATABASE IF NOT EXISTS lunchdb;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 생성 쿼리 실행&lt;/p&gt;
&lt;pre id=&quot;code_1785664449487&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use lunchdb;

-- tb_lunch_menu table이 있으면 삭제
DROP TABLE IF EXISTS tb_lunch_menu;

-- TEST_TB table 생성
CREATE TABLE IF NOT EXISTS tb_lunch_menu (
  seq int NOT NULL AUTO_INCREMENT COMMENT '점심 메뉴 일련번호',
  menu_name varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '점심 메뉴 명',
  reg_dtm datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일',
  PRIMARY KEY (`seq`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='점심 메뉴&amp;rsquo;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 데이터 등록 쿼리 실행&lt;/p&gt;
&lt;pre id=&quot;code_1785664460842&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO tb_lunch_menu (menu_name, reg_dtm) VALUES('짜장면', '2025-01-17 13:37:00.000');
INSERT INTO tb_lunch_menu (menu_name, reg_dtm) VALUES('짬봉', '2025-01-17 13:37:14.000');
INSERT INTO tb_lunch_menu (menu_name, reg_dtm) VALUES('볶음밥', '2025-01-17 13:37:26.000');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. DB 연동을 위한 MariaDB Connector 설치 (터미널에서 실행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1785664620580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install mariadb --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Xi8J/dJMcagfmdpy/9DzlzlhUAzTTcFW18oWskK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Xi8J/dJMcagfmdpy/9DzlzlhUAzTTcFW18oWskK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Xi8J/dJMcagfmdpy/9DzlzlhUAzTTcFW18oWskK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Xi8J%2FdJMcagfmdpy%2F9DzlzlhUAzTTcFW18oWskK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connection&amp;nbsp;Pools&amp;nbsp;with&amp;nbsp;MariaDB&amp;nbsp;Connector/Node.js&amp;nbsp;(Promise&amp;nbsp;API) &lt;br /&gt;(&lt;a href=&quot;https://mariadb.com/docs/server/connect/programming-languages/nodejs/promise/connection-pools/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mariadb.com/docs/server/connect/programming-languages/nodejs/promise/connection-pools/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/piXKo/dJMcajiSCDC/0quMLnbU99xD1MeZVbmJR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/piXKo/dJMcajiSCDC/0quMLnbU99xD1MeZVbmJR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/piXKo/dJMcajiSCDC/0quMLnbU99xD1MeZVbmJR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpiXKo%2FdJMcajiSCDC%2F0quMLnbU99xD1MeZVbmJR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;dateStrings를 true로 설정 &amp;ndash; 날짜를 문자열로 처리하도록 설정함 &amp;rarr; time_zone 문제 해결 &lt;br /&gt;(&lt;a href=&quot;https://mariadb.com/kb/en/node-js-connection-options/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mariadb.com/kb/en/node-js-connection-options/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tfse1/dJMcadplTUn/cXqnPk1XXnk8mZZvJCiT60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tfse1/dJMcadplTUn/cXqnPk1XXnk8mZZvJCiT60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tfse1/dJMcadplTUn/cXqnPk1XXnk8mZZvJCiT60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTfse1%2FdJMcadplTUn%2FcXqnPk1XXnk8mZZvJCiT60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;9. DB Connection Pool 생성 (src\db\mariadb 디렉터리 생성, connection.js 파일 생성, DB Connection Pool 생성)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdWpUJ/dJMcad3Xcv9/zntFkaWqkE32RkMVZrTV6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdWpUJ/dJMcad3Xcv9/zntFkaWqkE32RkMVZrTV6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdWpUJ/dJMcad3Xcv9/zntFkaWqkE32RkMVZrTV6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdWpUJ%2FdJMcad3Xcv9%2FzntFkaWqkE32RkMVZrTV6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785664841323&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const mariadb = require('mariadb');

const pool = mariadb.createPool({
    host: &quot;127.0.0.1&quot;,
    port: 3306,
    user: &quot;root&quot;,
    password: &quot;kiki@2000&quot;,
    database: &quot;lunchdb&quot;,
    connectionLimit: 10,
    dateStrings: true
});

async function getDBConnection() {
    connection = await pool.getConnection();
    return connection;
}

module.exports = getDBConnection;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;password는 Mariadb를 설치할 때 입력한 password로 입력하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 점심 메뉴 API 개발 (src 디렉터리에서 api\v1 디렉터리 생성, lunchmenu.js 파일 생성)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Kkaz/dJMcagl6kcX/t80etoIWqukrZ1m6TahM3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Kkaz/dJMcagl6kcX/t80etoIWqukrZ1m6TahM3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Kkaz/dJMcagl6kcX/t80etoIWqukrZ1m6TahM3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Kkaz%2FdJMcagl6kcX%2Ft80etoIWqukrZ1m6TahM3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785665069170&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express  = require('express');
const router = express.Router();
const getDBConnection = require('../../db/mariadb/connection');

// 전체 점심 메뉴 리스트를 리턴합니다.
router.get('/', async function(req, res, next) {
    console.log(&quot;REST API Get Method - Read All Lunch Menu.&quot;);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: []
    }

    const query = &quot;SELECT * FROM tb_lunch_menu ORDER BY REG_DTM DESC&quot;;
    
    let conn;
    try {
        conn = await getDBConnection();
        data = await conn.query(query, []);
        if (data.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            result.message = &quot;데이터가 조회되었습니다.&quot;;
            result.data = data;
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

// 점심 메뉴 번호로 검색하여 점심 메뉴를 리턴합니다.
router.get('/:no', async function(req, res, next) {
    console.log(`REST API Get Method - Read Lunch Menu ${req.params.no}.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    }

    const query = &quot;SELECT * FROM tb_lunch_menu WHERE seq = ?&quot;;

    let conn;
    try {
        conn = await getDBConnection();
        data = await conn.query(query, [req.params.no]);
        if (data.length == 0) {
            result.message = &quot;데이터가 없습니다.&quot;;
        } else {
            result.message = &quot;데이터가 조회되었습니다.&quot;;
            result.data = data[0];
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

// 점심 메뉴를 등록합니다.
router.post('/', async function(req, res, next) {
    console.log(&quot;REST API Post Method - Create Lunch Menu.&quot;);
    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    }

    if (req.body.menuName == undefined || req.body.menuName == &quot;&quot;) {
        result.result = &quot;fail&quot;;
        result.code = 400;
        result.message = &quot;menuName이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }

    const query = &quot;INSERT INTO tb_lunch_menu (menu_name) VALUES(?)&quot;;

    let conn;
    try {
        conn = await getDBConnection();
        data = await conn.query(query, [req.body.menuName]);
        // OkPacket { affectedRows: 1, insertId: 4n, warningStatus: 0 }
        if (data.affectedRows == 1) {
            result.message = &quot;데이터가 등록되었습니다.&quot;;
        } else {
            result.message = &quot;데이터가 등록되지 않았습니다.&quot;;
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

// 점심 메뉴를 수정합니다.
router.put('/:no', async function(req, res, next) {
    console.log(`REST API Put Method - Update Lunch Menu ${req.params.no}.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    }

    if (req.body.menuName == undefined || req.body.menuName == &quot;&quot;) {
        result.result = &quot;fail&quot;;
        result.code = 400;
        result.message = &quot;menuName이 없습니다.&quot;
        res.status(result.code).json(result);
        return;
    }
    const query = &quot;UPDATE tb_lunch_menu SET menu_name = ? WHERE seq = ?&quot;;
    
    let conn;
    try {
        conn = await getDBConnection();
        data = await conn.query(query, [req.body.menuName, req.params.no]);
        // OkPacket { affectedRows: 1, insertId: 0n, warningStatus: 0 }
        if (data.affectedRows == 1) {
            result.message = &quot;데이터가 수정되었습니다.&quot;;
        } else {
            result.message = &quot;데이터가 수정되지 않았습니다.&quot;;
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

// 점심 메뉴를 삭제제합니다.
router.delete('/:no', async function(req, res, next) {
    console.log(`REST API Delete Method - Delete Lunch Menu ${req.params.no}.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    }

    const query = &quot;DELETE FROM tb_lunch_menu WHERE seq = ?&quot;;
    
    let conn;
    try {
        conn = await getDBConnection();
        data = await conn.query(query, [req.params.no]);
        // OkPacket { affectedRows: 1, insertId: 0n, warningStatus: 0 }
        if (data.affectedRows == 1) {
            result.message = &quot;데이터가 삭제되었습니다.&quot;;
        } else {
            result.message = &quot;데이터가 삭제되지 않았습니다.&quot;;
        }
    } catch (error) {
        result.result = &quot;fail&quot;;
        result.code = 500;
        if (error.code == &quot;ER_NO_SUCH_TABLE&quot;) {
            result.message = &quot;테이블이 없습니다.&quot;;
        } else {
            result.message = &quot;서버 오류가 발생하였습니다.&quot;;
        }
        console.log(error);
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

module.exports = router;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. index.js 파일에 BODY를 JSON 데이터로 변환, URL 인코딩, 요청 리소스 허용 처리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zNiaR/dJMcadv3XZ0/3NkOlxgEiUb0pTjpOoDK61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zNiaR/dJMcadv3XZ0/3NkOlxgEiUb0pTjpOoDK61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zNiaR/dJMcadv3XZ0/3NkOlxgEiUb0pTjpOoDK61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzNiaR%2FdJMcadv3XZ0%2F3NkOlxgEiUb0pTjpOoDK61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;836&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785665332082&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const app = express();
const port = 9000;

app.get('/', (req, res) =&amp;gt; {
    res.send('Hello World!');
});

// Request의 내용(body)을 JSON 데이터로 변화해서 사용할 수 설정
app.use(express.json());
// express.urlencoded() 메서드를 이용하여 URL를 인코딩 할 수 있게 설정
app.use(express.urlencoded({extended:false}));
// 모든 리소스에 접근 허용 - CORS(Cross-origin resource sharing, 교차 출처 리소스 공유) 문제 해결
// GET, POST, PUT, DELETE 메서드 사용 허용
// content-type 요청 허용
app.use(function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.header('Access-Control-Allow-Headers', 'content-type');
    next();
});

// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));

app.listen(port, () =&amp;gt; {
    console.log(`APIServer started on port ${port}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. 웹 브라우저에서 확인 (http://127.0.0.1:9000/api/v1/lunchmenus)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGk8iJ/dJMcahL5xJU/33XAK47bEc3ssKxlnDO9i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGk8iJ/dJMcahL5xJU/33XAK47bEc3ssKxlnDO9i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGk8iJ/dJMcahL5xJU/33XAK47bEc3ssKxlnDO9i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGk8iJ%2FdJMcahL5xJU%2F33XAK47bEc3ssKxlnDO9i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;b&gt;점심 메뉴 API 테스트 (POSTMAN에서 확인)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 전체 점심 메뉴 조회&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/birhy2/dJMcabdX34E/0senrNT98xwKFFXeTkOqlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/birhy2/dJMcabdX34E/0senrNT98xwKFFXeTkOqlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/birhy2/dJMcabdX34E/0senrNT98xwKFFXeTkOqlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbirhy2%2FdJMcabdX34E%2F0senrNT98xwKFFXeTkOqlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;840&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 점심&amp;nbsp;메뉴&amp;nbsp;번호로&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;1535&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m22Xa/dJMcabZvedq/VhaeHmcIJVJf3nzc6vnIb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m22Xa/dJMcabZvedq/VhaeHmcIJVJf3nzc6vnIb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m22Xa/dJMcabZvedq/VhaeHmcIJVJf3nzc6vnIb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm22Xa%2FdJMcabZvedq%2FVhaeHmcIJVJf3nzc6vnIb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;839&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;839&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;3. 점심&amp;nbsp;메뉴&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;1535&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAtmYz/dJMcaiKXwag/SBlP6OfdIZUynH1UUCHmUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAtmYz/dJMcaiKXwag/SBlP6OfdIZUynH1UUCHmUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAtmYz/dJMcaiKXwag/SBlP6OfdIZUynH1UUCHmUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAtmYz%2FdJMcaiKXwag%2FSBlP6OfdIZUynH1UUCHmUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;839&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 점심&amp;nbsp;메뉴&amp;nbsp;번호로&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;1535&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UWnNS/dJMcaaGiGox/M8iFNQHXhr2qqzXRrLkg1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UWnNS/dJMcaaGiGox/M8iFNQHXhr2qqzXRrLkg1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UWnNS/dJMcaaGiGox/M8iFNQHXhr2qqzXRrLkg1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUWnNS%2FdJMcaaGiGox%2FM8iFNQHXhr2qqzXRrLkg1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;839&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;839&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;5. 점심&amp;nbsp;메뉴&amp;nbsp;번호로&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;1535&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3mCAT/dJMcah6uG1H/QH0QY2bwumKOEoyAlw5kFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3mCAT/dJMcah6uG1H/QH0QY2bwumKOEoyAlw5kFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3mCAT/dJMcah6uG1H/QH0QY2bwumKOEoyAlw5kFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3mCAT%2FdJMcah6uG1H%2FQH0QY2bwumKOEoyAlw5kFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;839&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;839&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Vue.js 3 &amp;amp; NodeJS/사내교육 - 점심 메뉴</category>
      <category>createPool</category>
      <category>Express.js</category>
      <category>mariaDB</category>
      <category>node.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/325</guid>
      <comments>https://carrotweb.tistory.com/325#entry325comment</comments>
      <pubDate>Sun, 2 Aug 2026 19:16:33 +0900</pubDate>
    </item>
    <item>
      <title>LangChain &amp;ndash; Advanced RAG, Post-Retrieval, Reranker</title>
      <link>https://carrotweb.tistory.com/324</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Reranker (리랭커)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터&amp;nbsp;스토어에서&amp;nbsp;검색된&amp;nbsp;문서(청크)가&amp;nbsp;사용자의&amp;nbsp;의도를&amp;nbsp;정확히&amp;nbsp;반영하지&amp;nbsp;못하거나&amp;nbsp;대화의&amp;nbsp;맥락을&amp;nbsp;놓칠&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;사용자&amp;nbsp;질문과&amp;nbsp;관련이&amp;nbsp;높은&amp;nbsp;문서(청크)로&amp;nbsp;순위를&amp;nbsp;재정렬합니다.&amp;nbsp;(프롬프트가&amp;nbsp;길어지면&amp;nbsp;중간에&amp;nbsp;위치한&amp;nbsp;중요한&amp;nbsp;정보를&amp;nbsp;LLM이&amp;nbsp;제대로&amp;nbsp;반영하지&amp;nbsp;못하는&amp;nbsp;&amp;lsquo;Lost&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Middle&amp;rsquo;&amp;nbsp;현상이&amp;nbsp;발생할&amp;nbsp;위험이&amp;nbsp;있습니다.&amp;nbsp;-&amp;nbsp;&lt;a href=&quot;https://arxiv.org/pdf/2307.03172)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://arxiv.org/pdf/2307.03172&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYiEiT/dJMcaafauF3/kuopWIYq98kf1uS3SN7AU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYiEiT/dJMcaafauF3/kuopWIYq98kf1uS3SN7AU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYiEiT/dJMcaafauF3/kuopWIYq98kf1uS3SN7AU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYiEiT%2FdJMcaafauF3%2FkuopWIYq98kf1uS3SN7AU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;328&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Retriever (검색) : 벡터 스토어에서 질문과 관련성(유사도 계산) 있는 문서(청크)들을 빠르게 추출합니다.&lt;/li&gt;
&lt;li&gt;Reranker (순위 재정렬) : 문서(청크)들을 분석(의미적 분석)하여 순위를 조정합니다.&lt;/li&gt;
&lt;li&gt;Compression&amp;nbsp;(압축)&amp;nbsp;:&amp;nbsp;전체&amp;nbsp;문서(청크)를&amp;nbsp;LLM에&amp;nbsp;전달하면&amp;nbsp;비용이&amp;nbsp;많이&amp;nbsp;드는&amp;nbsp;문제점을&amp;nbsp;해결하기&amp;nbsp;위해서&amp;nbsp;압축합니다.&lt;/li&gt;
&lt;/ol&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;/p&gt;
&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;시간&amp;nbsp;증가&amp;nbsp;:&amp;nbsp;복잡한&amp;nbsp;연산으로&amp;nbsp;처리&amp;nbsp;시간이&amp;nbsp;길어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reranking&amp;nbsp;&amp;ndash;&amp;nbsp;Cross&amp;nbsp;Encoder&amp;nbsp;Reranker&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bi-Encoder&amp;nbsp;(단일&amp;nbsp;인코더)&amp;nbsp;vs&amp;nbsp;Cross&amp;nbsp;Encoder&amp;nbsp;(교차&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;749&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckJKps/dJMcaixu3ZU/FQ8QLNvt35l0SWEoLZbbq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckJKps/dJMcaixu3ZU/FQ8QLNvt35l0SWEoLZbbq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckJKps/dJMcaixu3ZU/FQ8QLNvt35l0SWEoLZbbq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckJKps%2FdJMcaixu3ZU%2FFQ8QLNvt35l0SWEoLZbbq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;334&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;417&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;Bi-Encoder&amp;nbsp;방식은&amp;nbsp;질의와&amp;nbsp;문서를&amp;nbsp;각각&amp;nbsp;독립적으로&amp;nbsp;임베딩한&amp;nbsp;후,&amp;nbsp;두&amp;nbsp;임베딩&amp;nbsp;벡터&amp;nbsp;간의&amp;nbsp;유사도를&amp;nbsp;통해&amp;nbsp;문서를&amp;nbsp;평가하는&amp;nbsp;방식입니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cross-Encoder는 모든 질의-문서 쌍을 개별적으로 모델이 계산해야 평가하는 방식입니다. (처리 속도가 느립니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bi-Encoder&amp;nbsp;기반의&amp;nbsp;벡터&amp;nbsp;검색이&amp;nbsp;추출한&amp;nbsp;후보&amp;nbsp;문서(상위&amp;nbsp;K개&amp;nbsp;이하)를&amp;nbsp;대상으로&amp;nbsp;2차적으로&amp;nbsp;리랭킹할&amp;nbsp;때&amp;nbsp;사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cross&amp;nbsp;Encoder&amp;nbsp;Reranker&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교차 인코더 모델을 구현한 Hugging Face 모델(예: BAAI/bge-reranker-base)를 사용하여 리트리버에서 리랭커를 구현하는 방법을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SagemakerEndpointCrossEncoder를 사용하면 Sagemaker에 로드된 HuggingFace 모델을 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ6x3q/dJMcac43WsE/WNMvpO0mPlAmOKXIAYkk51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ6x3q/dJMcac43WsE/WNMvpO0mPlAmOKXIAYkk51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ6x3q/dJMcac43WsE/WNMvpO0mPlAmOKXIAYkk51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ6x3q%2FdJMcac43WsE%2FWNMvpO0mPlAmOKXIAYkk51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Reranking하기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o3iFm/dJMcaaztmjK/F9Ak7wdNU6VgDkMDoO3eGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o3iFm/dJMcaaztmjK/F9Ak7wdNU6VgDkMDoO3eGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o3iFm/dJMcaaztmjK/F9Ak7wdNU6VgDkMDoO3eGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo3iFm%2FdJMcaaztmjK%2FF9Ak7wdNU6VgDkMDoO3eGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;BAAI/bge-reranker-base&amp;nbsp;(&lt;a href=&quot;https://huggingface.co/BAAI/bge-reranker-base&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/BAAI/bge-reranker-base&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bge-m3&amp;nbsp;모델(BAAI(Beijing&amp;nbsp;Academy&amp;nbsp;of&amp;nbsp;Artificial&amp;nbsp;Intelligence)에서&amp;nbsp;개발한&amp;nbsp;최신&amp;nbsp;임베딩&amp;nbsp;모델)를&amp;nbsp;기반으로&amp;nbsp;한&amp;nbsp;리랭커&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsh2i6/dJMcad3VGIS/E7qhcZmhku6luB1O7WoNn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsh2i6/dJMcad3VGIS/E7qhcZmhku6luB1O7WoNn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsh2i6/dJMcad3VGIS/E7qhcZmhku6luB1O7WoNn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsh2i6%2FdJMcad3VGIS%2FE7qhcZmhku6luB1O7WoNn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;BAAI/bge-reranker-v2-m3&amp;nbsp;(&lt;a href=&quot;https://huggingface.co/BAAI/bge-reranker-v2-m3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/BAAI/bge-reranker-v2-m3&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bge-m3&amp;nbsp;모델(BAAI(Beijing&amp;nbsp;Academy&amp;nbsp;of&amp;nbsp;Artificial&amp;nbsp;Intelligence)에서&amp;nbsp;개발한&amp;nbsp;최신&amp;nbsp;임베딩&amp;nbsp;모델)를&amp;nbsp;기반으로&amp;nbsp;한&amp;nbsp;다국어&amp;nbsp;리랭커&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfn88R/dJMb99NYO83/jKWc2lrbpmCC7UxcHXLxJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfn88R/dJMb99NYO83/jKWc2lrbpmCC7UxcHXLxJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfn88R/dJMb99NYO83/jKWc2lrbpmCC7UxcHXLxJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfn88R%2FdJMb99NYO83%2FjKWc2lrbpmCC7UxcHXLxJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;크로스&amp;nbsp;인코더&amp;nbsp;테스트&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785407297550&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
 
# Reranker
reranker = HuggingFaceCrossEncoder(
    model_name='BAAI/bge-reranker-v2-m3',
    model_kwargs = {'device': 'cpu'}
)

[Code]
text1 = &quot;중학생의 수학 기초학력 미달률은?&quot;
text2 = &quot;중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며&quot;
text3 = &quot;수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황&quot;

similar_score_1 = reranker.score([text1, text2])
similar_score_2 = reranker.score([text1, text3])
print(similar_score_1, similar_score_2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 크로스&amp;nbsp;인코더&amp;nbsp;테스트&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JrDeZ/dJMcaalPcYw/bGqAfFSYL7dDHqDeg0ywS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JrDeZ/dJMcaalPcYw/bGqAfFSYL7dDHqDeg0ywS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JrDeZ/dJMcaalPcYw/bGqAfFSYL7dDHqDeg0ywS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJrDeZ%2FdJMcaalPcYw%2FbGqAfFSYL7dDHqDeg0ywS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;831&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;831&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;3. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;검색&amp;nbsp;결과&amp;nbsp;순위&amp;nbsp;재정렬&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785407369881&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker

compressor = CrossEncoderReranker(model=reranker, top_n=3)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

question = &quot;중학생의 수학 기초학력 미달률은?&quot;

compressed_docs = compression_retriever.invoke(question)
print(compressed_docs)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 검색&amp;nbsp;결과&amp;nbsp;순위&amp;nbsp;재정렬&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1bLyS/dJMcafHzqnc/7aOtK3Rp8LBGqiF9pDGu5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1bLyS/dJMcafHzqnc/7aOtK3Rp8LBGqiF9pDGu5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1bLyS/dJMcafHzqnc/7aOtK3Rp8LBGqiF9pDGu5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1bLyS%2FdJMcafHzqnc%2F7aOtK3Rp8LBGqiF9pDGu5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;831&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과 순서 재정렬 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[BAAI/bge-reranker-base를 사용할 경우]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(id='fb8315b4-483c-4763-b361-af94b8e80ce0', metadata={'source': 'data/test.txt'}, page_content='교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학'), Document(id='6dcbfe08-1013-45d4-8d20-fcdbaae62393', metadata={'source': 'data/test.txt'}, page_content='8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.'), Document(id='241efdba-09b4-4e44-a74b-bcbd645eeb1e', metadata={'source': 'data/test.txt'}, page_content='테크빌교육 체더스, 기초학력 부진 해결을 위한 코스웨어 기획전 진행')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;[BAAI/bge-reranker-v2-m3를 사용할 경우]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(id='fb8315b4-483c-4763-b361-af94b8e80ce0', metadata={'source': 'data/test.txt'}, page_content='교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학'), Document(id='6dcbfe08-1013-45d4-8d20-fcdbaae62393', metadata={'source': 'data/test.txt'}, page_content='8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.'), Document(id='2790ef3b-dab5-4a0e-8595-c054b6cdce53', metadata={'source': 'data/test.txt'}, page_content='이번 기획전에서는 AI 수학 학습 플랫폼 &amp;lsquo;매쓰홀릭 T&amp;rsquo;, AI 영어 학습 솔루션 &amp;lsquo;원아워&amp;rsquo;, 국어 문해력 학습 프로그램 &amp;lsquo;리드포스쿨&amp;rsquo; 등 기초 학력 향상에 특화된 에듀테크')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국어 Reranker 사용하기&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;Dongjin-kr/ko-reranker&amp;nbsp;(&lt;a href=&quot;https://huggingface.co/Dongjin-kr/ko-reranker&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/Dongjin-kr/ko-reranker&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M4j6M/dJMcaaTIVEw/irCzhrXPPYnSTqkGEjG7WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M4j6M/dJMcaaTIVEw/irCzhrXPPYnSTqkGEjG7WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M4j6M/dJMcaaTIVEw/irCzhrXPPYnSTqkGEjG7WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM4j6M%2FdJMcaaTIVEw%2FirCzhrXPPYnSTqkGEjG7WK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;검색&amp;nbsp;결과&amp;nbsp;순위&amp;nbsp;재정렬&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785407685523&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

# 교차 인코더 리랭커
cross_encoder_model = HuggingFaceCrossEncoder(model_name=&quot;Dongjin-kr/ko-reranker&quot;)

compressor = CrossEncoderReranker(model=cross_encoder_model, top_n=3)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

question = &quot;중학생의 수학 기초학력 미달률은?&quot;

compressed_docs = compression_retriever.invoke(question)

print(compressed_docs)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 검색&amp;nbsp;결과&amp;nbsp;순위&amp;nbsp;재정렬&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v0CW3/dJMcaiqFqBZ/W5sfU7ZcD1KEBCRK43yW30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v0CW3/dJMcaiqFqBZ/W5sfU7ZcD1KEBCRK43yW30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v0CW3/dJMcaiqFqBZ/W5sfU7ZcD1KEBCRK43yW30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv0CW3%2FdJMcaiqFqBZ%2FW5sfU7ZcD1KEBCRK43yW30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과 순서 재정렬 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(id='fb8315b4-483c-4763-b361-af94b8e80ce0',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='교육부의&amp;nbsp;&amp;lsquo;2023&amp;nbsp;국가수준&amp;nbsp;학업성취도평가&amp;rsquo;에&amp;nbsp;따르면&amp;nbsp;중학생의&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;9.1%,&amp;nbsp;영어&amp;nbsp;6.0%,&amp;nbsp;수학&amp;nbsp;13.0%였으며&amp;nbsp;고등학생은&amp;nbsp;국어&amp;nbsp;8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학'),&amp;nbsp;Document(id='6dcbfe08-1013-45d4-8d20-fcdbaae62393',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학&amp;nbsp;16.6%로&amp;nbsp;집계됐다.&amp;nbsp;특히&amp;nbsp;수학의&amp;nbsp;기초학력&amp;nbsp;부진이&amp;nbsp;가장&amp;nbsp;높아&amp;nbsp;학습&amp;nbsp;격차&amp;nbsp;해소를&amp;nbsp;위한&amp;nbsp;지원이&amp;nbsp;필요한&amp;nbsp;상황이다.'),&amp;nbsp;Document(id='2790ef3b-dab5-4a0e-8595-c054b6cdce53',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='이번&amp;nbsp;기획전에서는&amp;nbsp;AI&amp;nbsp;수학&amp;nbsp;학습&amp;nbsp;플랫폼&amp;nbsp;&amp;lsquo;매쓰홀릭&amp;nbsp;T&amp;rsquo;,&amp;nbsp;AI&amp;nbsp;영어&amp;nbsp;학습&amp;nbsp;솔루션&amp;nbsp;&amp;lsquo;원아워&amp;rsquo;,&amp;nbsp;국어&amp;nbsp;문해력&amp;nbsp;학습&amp;nbsp;프로그램&amp;nbsp;&amp;lsquo;리드포스쿨&amp;rsquo;&amp;nbsp;등&amp;nbsp;기초&amp;nbsp;학력&amp;nbsp;향상에&amp;nbsp;특화된&amp;nbsp;에듀테크')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크로스&amp;nbsp;인코더의&amp;nbsp;스코어로&amp;nbsp;사용하여&amp;nbsp;순서&amp;nbsp;재정렬하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;크로스&amp;nbsp;인코더의&amp;nbsp;스코어로&amp;nbsp;사용하여&amp;nbsp;리랭커&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785407809957&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
question = &quot;중학생의 수학 기초학력 미달률은?&quot;

# 스코어
idx = 0
for text in result_texts:
    similar_score = reranker.score([question, text.page_content])
    text.metadata['score'] = similar_score
    if similar_score &amp;lt; 0.5:
        del result_texts[idx]
    #print(idx, text.metadata['score'], text.page_content)
    idx += 1

# 재정렬 (내림차순)
sorted_result_texts = sorted(result_texts, key=lambda item: item.metadata['score'], reverse=True)

for text in sorted_result_texts:
    print(text.metadata['score'], text.page_content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 크로스&amp;nbsp;인코더의&amp;nbsp;스코어로&amp;nbsp;사용하여&amp;nbsp;리랭커&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgMQdx/dJMcacYjH88/RfSqKlym5xQABB9PE53wO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgMQdx/dJMcacYjH88/RfSqKlym5xQABB9PE53wO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgMQdx/dJMcacYjH88/RfSqKlym5xQABB9PE53wO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgMQdx%2FdJMcacYjH88%2FRfSqKlym5xQABB9PE53wO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;831&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FlashRank&amp;nbsp;Reranker&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlashRank(프랭크 랭크)는 기존 검색과 리트리벌 파이프라인에 순위 재정렬(Reranking) 기능을 추가하는 초경량 &amp;amp; 초고속 파이썬 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SoTA(State-of-the-Art)&amp;nbsp;크로스&amp;nbsp;인코더를&amp;nbsp;기반으로&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csTaVf/dJMcahZzX7D/4XeiHbzcwVmaPraxQDFWn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csTaVf/dJMcahZzX7D/4XeiHbzcwVmaPraxQDFWn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csTaVf/dJMcahZzX7D/4XeiHbzcwVmaPraxQDFWn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsTaVf%2FdJMcahZzX7D%2F4XeiHbzcwVmaPraxQDFWn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;검색&amp;nbsp;결과&amp;nbsp;순위&amp;nbsp;재정렬&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785407927265&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
검색 결과 재순위(Re-ranking)

[Code]
%pip install flashrank

[Code]
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import FlashrankRerank

# 플래시 링크
compressor = FlashrankRerank(model=&quot;ms-marco-MultiBERT-L-12&quot;, top_n=3)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

question = &quot;중학생의 수학 기초학력 미달률은?&quot;

compressed_docs = compression_retriever.invoke(question)

print(compressed_docs)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 첫번째 [Code]를 실행하여 flashrank를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3eFcu/dJMcahk6g5G/3coA2kzkI5Fgu4XukaL9Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3eFcu/dJMcahk6g5G/3coA2kzkI5Fgu4XukaL9Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3eFcu/dJMcahk6g5G/3coA2kzkI5Fgu4XukaL9Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3eFcu%2FdJMcahk6g5G%2F3coA2kzkI5Fgu4XukaL9Pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치&amp;nbsp;후&amp;nbsp;비로&amp;nbsp;실행하면&amp;nbsp;오류가&amp;nbsp;발생합니다.&amp;nbsp;VSCode를&amp;nbsp;재시작해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 검색&amp;nbsp;결과&amp;nbsp;순위&amp;nbsp;재정렬&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AIbCO/dJMcahyzdxw/IOdu2jXgqnEY4dLrkokY2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AIbCO/dJMcahyzdxw/IOdu2jXgqnEY4dLrkokY2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AIbCO/dJMcahyzdxw/IOdu2jXgqnEY4dLrkokY2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAIbCO%2FdJMcahyzdxw%2FIOdu2jXgqnEY4dLrkokY2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과 순서 재정렬 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(metadata={'id':&amp;nbsp;3,&amp;nbsp;'relevance_score':&amp;nbsp;np.float32(0.9995719),&amp;nbsp;'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='기초&amp;nbsp;학력&amp;nbsp;향상에&amp;nbsp;특화된&amp;nbsp;에듀테크&amp;nbsp;솔루션을&amp;nbsp;특별&amp;nbsp;할인가로&amp;nbsp;제공한다.'),&amp;nbsp;Document(metadata={'id':&amp;nbsp;4,&amp;nbsp;'relevance_score':&amp;nbsp;np.float32(0.99949664),&amp;nbsp;'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='이번&amp;nbsp;기획전에서는&amp;nbsp;AI&amp;nbsp;수학&amp;nbsp;학습&amp;nbsp;플랫폼&amp;nbsp;&amp;lsquo;매쓰홀릭&amp;nbsp;T&amp;rsquo;,&amp;nbsp;AI&amp;nbsp;영어&amp;nbsp;학습&amp;nbsp;솔루션&amp;nbsp;&amp;lsquo;원아워&amp;rsquo;,&amp;nbsp;국어&amp;nbsp;문해력&amp;nbsp;학습&amp;nbsp;프로그램&amp;nbsp;&amp;lsquo;리드포스쿨&amp;rsquo;&amp;nbsp;등&amp;nbsp;기초&amp;nbsp;학력&amp;nbsp;향상에&amp;nbsp;특화된&amp;nbsp;에듀테크'),&amp;nbsp;Document(metadata={'id':&amp;nbsp;2,&amp;nbsp;'relevance_score':&amp;nbsp;np.float32(0.9993681),&amp;nbsp;'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='테크빌교육&amp;nbsp;체더스,&amp;nbsp;기초학력&amp;nbsp;부진&amp;nbsp;해결을&amp;nbsp;위한&amp;nbsp;코스웨어&amp;nbsp;기획전&amp;nbsp;진행')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 하위 순위가 더 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(metadata={'id':&amp;nbsp;0,&amp;nbsp;'relevance_score':&amp;nbsp;np.float32(0.99928063),&amp;nbsp;'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='교육부의&amp;nbsp;&amp;lsquo;2023&amp;nbsp;국가수준&amp;nbsp;학업성취도평가&amp;rsquo;에&amp;nbsp;따르면&amp;nbsp;중학생의&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;9.1%,&amp;nbsp;영어&amp;nbsp;6.0%,&amp;nbsp;수학&amp;nbsp;13.0%였으며&amp;nbsp;고등학생은&amp;nbsp;국어&amp;nbsp;8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학'),&amp;nbsp;Document(metadata={'id':&amp;nbsp;1,&amp;nbsp;'relevance_score':&amp;nbsp;np.float32(0.9986457),&amp;nbsp;'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학&amp;nbsp;16.6%로&amp;nbsp;집계됐다.&amp;nbsp;특히&amp;nbsp;수학의&amp;nbsp;기초학력&amp;nbsp;부진이&amp;nbsp;가장&amp;nbsp;높아&amp;nbsp;학습&amp;nbsp;격차&amp;nbsp;해소를&amp;nbsp;위한&amp;nbsp;지원이&amp;nbsp;필요한&amp;nbsp;상황이다.')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;검색 결과에 대한&lt;span&gt; &lt;/span&gt;&lt;/span&gt;순서 재정렬이 항상 적합하지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델에 따라 다르니 모델 테스트를 통해 사용하시길 바랍니다.&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>Advanced RAG</category>
      <category>cross encoder</category>
      <category>HuggingFaceCrossEncoder</category>
      <category>Post-Retrieval</category>
      <category>reranker</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/324</guid>
      <comments>https://carrotweb.tistory.com/324#entry324comment</comments>
      <pubDate>Thu, 30 Jul 2026 19:47:10 +0900</pubDate>
    </item>
    <item>
      <title>LangChain &amp;ndash; Advanced RAG, Post-Retrieval, ContextualCompressionRetriever</title>
      <link>https://carrotweb.tistory.com/323</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;질문에 대한 데이터를 검색하면 질문과 관련된 많은 문서(청크)가 검색될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유료 LLM를 사용할 경우 검색된 많은 문서(청크)를 그대로 사용하면 많은 비용이 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 검색된 많은 문서(청크)를 그대로 사용하지 않고 질문의 맥락에 맞게 검색된 문서(청크)를 압축하여 사용하면 비용을 줄 일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SSVSR/dJMcagzCYUF/8ozFukZjq56IjTikPzxGSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SSVSR/dJMcagzCYUF/8ozFukZjq56IjTikPzxGSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SSVSR/dJMcagzCYUF/8ozFukZjq56IjTikPzxGSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSSVSR%2FdJMcagzCYUF%2F8ozFukZjq56IjTikPzxGSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;64&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Advanced RAG &amp;ndash; Post-Retrieval&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ContextualCompressionRetriever (문맥 압축 검색기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본&amp;nbsp;리트리버를&amp;nbsp;래핑하고&amp;nbsp;결과를&amp;nbsp;압축하는&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6rVcE/dJMcah6s4Ck/DukmNquCXRYOioYMq7uahk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6rVcE/dJMcah6s4Ck/DukmNquCXRYOioYMq7uahk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6rVcE/dJMcah6s4Ck/DukmNquCXRYOioYMq7uahk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6rVcE%2FdJMcah6s4Ck%2FDukmNquCXRYOioYMq7uahk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;b&gt;LLMChainExtractor (LLM 체인 추출기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 체인을 사용하여 문서(청크)에서 관련 부분을 추출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PqITH/dJMcacxfU95/3DL8vW9j4TArlogIftaqbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PqITH/dJMcacxfU95/3DL8vW9j4TArlogIftaqbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PqITH/dJMcacxfU95/3DL8vW9j4TArlogIftaqbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPqITH%2FdJMcacxfU95%2F3DL8vW9j4TArlogIftaqbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&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;project3에 QnA3.ipynb 파일에 검색 결과 압축 코드를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rgTgT/dJMcabkR9hL/lXOrFPq9SHEkzCIlST0zC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rgTgT/dJMcabkR9hL/lXOrFPq9SHEkzCIlST0zC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rgTgT/dJMcabkR9hL/lXOrFPq9SHEkzCIlST0zC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrgTgT%2FdJMcabkR9hL%2FlXOrFPq9SHEkzCIlST0zC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785403772248&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# LLM 체인 추출기
compressor = LLMChainExtractor.from_llm(model)

# 문맥 압축 검색기
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

compressed_docs = compression_retriever.invoke(&quot;중학생의 수학 기초학력 미달률은?&quot;)
print(compressed_docs)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 문서(청크)입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학&lt;/li&gt;
&lt;li&gt;8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.&lt;/li&gt;
&lt;li&gt;테크빌교육 체더스, 기초학력 부진 해결을 위한 코스웨어 기획전 진행&lt;/li&gt;
&lt;li&gt;기초 학력 향상에 특화된 에듀테크 솔루션을 특별 할인가로 제공한다.이번 기획전에서는 AI 수학 학습 플랫폼 &amp;lsquo;매쓰홀릭 T&amp;rsquo;, AI 영어 학습 솔루션 &amp;lsquo;원아워&amp;rsquo;, 국어 문해력 학습 프로그램 &amp;lsquo;리드포스쿨&amp;rsquo; 등 기초 학력 향상에 특화된 에듀테크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검색된 문서(청크)를 압축한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[gpt-4o-mini를 사용한 경우]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(metadata={'source': 'data/test.txt'}, page_content='중학생의 기초학력 미달률은 9.1%, 수학 13.0%였으며'), Document(metadata={'source': 'data/test.txt'}, page_content='수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[exaone3.5:7.8b를 사용한 경우]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Document(metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='중학생의&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;9.1%,&amp;nbsp;수학은&amp;nbsp;13.0%였습니다.&amp;nbsp;\n\n*Note:&amp;nbsp;The&amp;nbsp;extracted&amp;nbsp;part&amp;nbsp;directly&amp;nbsp;addresses&amp;nbsp;the&amp;nbsp;question&amp;nbsp;regarding&amp;nbsp;the&amp;nbsp;middle&amp;nbsp;school&amp;nbsp;mathematics&amp;nbsp;foundational&amp;nbsp;achievement&amp;nbsp;rate&amp;nbsp;while&amp;nbsp;keeping&amp;nbsp;the&amp;nbsp;provided&amp;nbsp;context&amp;nbsp;intact.*'),&amp;nbsp;Document(metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='수학&amp;nbsp;16.6%')] &lt;br /&gt;참고:&amp;nbsp;발췌한&amp;nbsp;부분은&amp;nbsp;제공된&amp;nbsp;맥락을&amp;nbsp;그대로&amp;nbsp;유지하면서&amp;nbsp;중학교&amp;nbsp;수학&amp;nbsp;기초&amp;nbsp;성취도에&amp;nbsp;대한&amp;nbsp;질문을&amp;nbsp;직접&amp;nbsp;다루고&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국어 LLM 사용시 ContextualCompressionRetriever 대신 프롬프트를&amp;nbsp;이용하여&amp;nbsp;추출하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;프롬프트로&amp;nbsp;추출하는&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785406686514&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 프롬프트 (한국어)
prompt_template = PromptTemplate.from_template(
&quot;&quot;&quot;다음은 입력 문장과 여러 개의 보기 문장입니다.
입력 문장과 의미가 유사하거나 거의 같은 내용을 가진 보기 문장을 **그대로** 추출하세요.  
추론하거나 새로운 문장을 생성하지 말고, 보기 문장 중 유사한 문장을 **있는 그대로** 출력하세요.  

### 입력 문장:
{question}

### 보기 문장:
{context}

### 출력 형식:
보기 문장 그대로 출력하세요.
부가 설명은 추가하지 마세요.
&quot;&quot;&quot;
)

texts = &quot;\n\n&quot;.join(result_text.page_content for result_text in result_texts)

question = &quot;중학생의 수학 기초학력 미달률은?&quot;

chain = (
    prompt_template
    | model
    | StrOutputParser()
)

response = chain.invoke({&quot;context&quot;: texts, &quot;question&quot;: question})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프롬프트로&amp;nbsp;추출하는&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYmpHL/dJMcaa0ud1a/ZpL9MbWlF4V2ZXgrMek1zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYmpHL/dJMcaa0ud1a/ZpL9MbWlF4V2ZXgrMek1zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYmpHL/dJMcaa0ud1a/ZpL9MbWlF4V2ZXgrMek1zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYmpHL%2FdJMcaa0ud1a%2FZpL9MbWlF4V2ZXgrMek1zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과 압축 실행 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[exaone3.5:7.8b를 사용한 경우]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육부의&amp;nbsp;&amp;lsquo;2023&amp;nbsp;국가수준&amp;nbsp;학업성취도평가&amp;rsquo;에&amp;nbsp;따르면&amp;nbsp;중학생의&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;9.1%,&amp;nbsp;영어&amp;nbsp;6.0%,&amp;nbsp;수학&amp;nbsp;13.0%였으며&amp;nbsp;고등학생은&amp;nbsp;국어&amp;nbsp;8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학&amp;nbsp;16.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학&amp;nbsp;16.6%로&amp;nbsp;집계됐다.&amp;nbsp;특히&amp;nbsp;수학의&amp;nbsp;기초학력&amp;nbsp;부진이&amp;nbsp;가장&amp;nbsp;높아&amp;nbsp;학습&amp;nbsp;격차&amp;nbsp;해소를&amp;nbsp;위한&amp;nbsp;지원이&amp;nbsp;필요한&amp;nbsp;상황이다. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&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;</description>
      <category>LLM Application/LangChain</category>
      <category>Advanced RAG</category>
      <category>Compression</category>
      <category>Post-Retrieval</category>
      <category>Retriever</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/323</guid>
      <comments>https://carrotweb.tistory.com/323#entry323comment</comments>
      <pubDate>Thu, 30 Jul 2026 19:22:25 +0900</pubDate>
    </item>
    <item>
      <title>LangChain &amp;ndash; Advanced RAG, Pre-Retrieval(한국어 LLM 사용)</title>
      <link>https://carrotweb.tistory.com/322</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 한국어 LLM인 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;exaone3.5:7.8b를 &lt;/span&gt;사용하여 답변을 생성해보고 MultiQueryRetriever에 적용해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Exaone&amp;nbsp;3.5&amp;nbsp;(&lt;a href=&quot;https://www.lgresearch.ai/exaone&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.lgresearch.ai/exaone&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQceAx/dJMcaiYq243/WuHtJe0Vn2m1fC0RTvKa70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQceAx/dJMcaiYq243/WuHtJe0Vn2m1fC0RTvKa70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQceAx/dJMcaiYq243/WuHtJe0Vn2m1fC0RTvKa70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQceAx%2FdJMcaiYq243%2FWuHtJe0Vn2m1fC0RTvKa70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;837&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현배 버전은 4.5입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ollama에서 Exaone 3.5 다운로드 하기&lt;/b&gt; &lt;br /&gt;1.&amp;nbsp;Ollama&amp;nbsp;Models&amp;nbsp;Search&amp;nbsp;(&lt;a href=&quot;https://ollama.com/search?q=exaone&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com/search?q=exaone&lt;/a&gt;) 에서 Exaone 3.5를 검색합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdXyVm/dJMcaaGg3Jt/es6JWEGHFuHQBxxWKhKfPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdXyVm/dJMcaaGg3Jt/es6JWEGHFuHQBxxWKhKfPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdXyVm/dJMcaaGg3Jt/es6JWEGHFuHQBxxWKhKfPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdXyVm%2FdJMcaaGg3Jt%2Fes6JWEGHFuHQBxxWKhKfPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;Exaone&amp;nbsp;3.5(&lt;a href=&quot;https://ollama.com/library/exaone3.5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com/library/exaone3.5&lt;/a&gt;)를&amp;nbsp;클릭하면&amp;nbsp;다양한&amp;nbsp;모델이&amp;nbsp;있습니다. &lt;br /&gt;PC에서&amp;nbsp;사용할거라&amp;nbsp;용량이&amp;nbsp;작은&amp;nbsp;모델인&amp;nbsp;Exaone&amp;nbsp;3.5를&amp;nbsp;사용할&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;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chzxRJ/dJMcad3VCkD/xUHJ0uYPPDTKqOlesfQPeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chzxRJ/dJMcad3VCkD/xUHJ0uYPPDTKqOlesfQPeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chzxRJ/dJMcad3VCkD/xUHJ0uYPPDTKqOlesfQPeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchzxRJ%2FdJMcad3VCkD%2FxUHJ0uYPPDTKqOlesfQPeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;exaone3.5:7.8b를&amp;nbsp;클릭하면&amp;nbsp;모델에&amp;nbsp;대한&amp;nbsp;정보를&amp;nbsp;확인할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7ucGO/dJMcahyy7Sj/uVW6OhQKxgps9kjvMWbryK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7ucGO/dJMcahyy7Sj/uVW6OhQKxgps9kjvMWbryK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7ucGO/dJMcahyy7Sj/uVW6OhQKxgps9kjvMWbryK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7ucGO%2FdJMcahyy7Sj%2FuVW6OhQKxgps9kjvMWbryK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;콘솔에서&amp;nbsp;ollama&amp;nbsp;pull&amp;nbsp;명령어를&amp;nbsp;사용하여&amp;nbsp;exaone3.5:7.8b를&amp;nbsp;다운로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785399535869&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama pull exaone3.5:7.8b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eIGzH0/dJMcahL31qI/y4JX6MzppPOk36x4Rpy031/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eIGzH0/dJMcahL31qI/y4JX6MzppPOk36x4Rpy031/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eIGzH0/dJMcahL31qI/y4JX6MzppPOk36x4Rpy031/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeIGzH0%2FdJMcahL31qI%2Fy4JX6MzppPOk36x4Rpy031%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;708&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔에서 ollama list 명령어를 사용하여 다운로드된 모델들을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Generation&amp;nbsp;(생성)&amp;nbsp;&amp;ndash;&amp;nbsp;LLM&amp;nbsp;(답변&amp;nbsp;생성)&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 QnA3.ipynb 파일에 Exaone으로 실행 코드를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785399637796&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_ollama import ChatOllama

model = ChatOllama(
    model=&quot;exaone3.5:7.8b&quot;,
    temperature=0
)

[Code]
from langchain import hub

prompt = hub.pull(&quot;rlm/rag-prompt&quot;)
print(prompt)

[Code]
retriever = loaded_vector_store.as_retriever(search_kwargs={&quot;k&quot;: 5})

result_texts = retriever.invoke(&quot;중학생의 수학 기초학력 미달률은?&quot;)
print(result_texts)

[Code]
from langchain_core.output_parsers import StrOutputParser

texts = &quot;\n\n&quot;.join(result_text.page_content for result_text in result_texts)

chain = prompt | model | StrOutputParser()
response = chain.invoke({&quot;context&quot; : texts, &quot;question&quot;: &quot;중학생의 수학 기초학력 미달률은?&quot;})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Exaone으로&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhSGNB/dJMcafHzlM1/AYXvQvJ7k4Y8fKV3KgKbFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhSGNB/dJMcafHzlM1/AYXvQvJ7k4Y8fKV3KgKbFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhSGNB/dJMcafHzlM1/AYXvQvJ7k4Y8fKV3KgKbFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhSGNB%2FdJMcafHzlM1%2FAYXvQvJ7k4Y8fKV3KgKbFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&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;Ollama 프롬프트 실행 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785399726365&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;중학생의 수학 기초학력 미달률은 13.0%입니다. 이는 전체 기초학력 미달률 중 수학 분야에서 가장 높은 비율을 보입니다. 학습 격차 해소를 위한 추가적인 지원이 필요한 상황입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context가&amp;nbsp;없을&amp;nbsp;경우&amp;nbsp;실행&amp;nbsp;결과입니다. &lt;/p&gt;
&lt;pre id=&quot;code_1785399749367&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;제공된 맥락에는 중학생의 수학 기초학력 미달률에 대한 구체적인 통계나 데이터가 포함되어 있지 않습니다. 정확한 비율을 말씀드리기 위해서는 해당 연도와 지역의 공식 교육 통계 자료가 필요합니다. 따라서 현재 정보로는 해당률을 알려드리지 못합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, Exaone3.5:32b를 다운로드하여 사용하고자 할 경우에는 시스템 사양이 낮을 경우 응답시간이 느려 사용할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Flh2k/dJMcaasHPou/atKTh2Xyy5k0amP5WostHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Flh2k/dJMcaasHPou/atKTh2Xyy5k0amP5WostHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Flh2k/dJMcaasHPou/atKTh2Xyy5k0amP5WostHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFlh2k%2FdJMcaasHPou%2FatKTh2Xyy5k0amP5WostHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;963&quot; height=&quot;151&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용량이 크기 때문에 작동이 되지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z6yU3/dJMcafHzlQd/02ibKEkEzGxwjXBXVZZk61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z6yU3/dJMcafHzlQd/02ibKEkEzGxwjXBXVZZk61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z6yU3/dJMcafHzlQd/02ibKEkEzGxwjXBXVZZk61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ6yU3%2FdJMcafHzlQd%2F02ibKEkEzGxwjXBXVZZk61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;963&quot; height=&quot;649&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;649&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;그럴 경우, 시스템 속성(Windows + R)에서 가상메모리의 페이징 파일 크기를 변경하시면 사용이 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyejek/dJMcadCMkbA/0KYcLLBo50hlnTm1YPmQx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyejek/dJMcadCMkbA/0KYcLLBo50hlnTm1YPmQx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyejek/dJMcadCMkbA/0KYcLLBo50hlnTm1YPmQx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcyejek%2FdJMcadCMkbA%2F0KYcLLBo50hlnTm1YPmQx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;674&quot; data-origin-width=&quot;908&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국어&amp;nbsp;LLM&amp;nbsp;사용시&amp;nbsp;Multi-Query&amp;nbsp;Retriever의&amp;nbsp;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 LLM를 Multi-Query Retriever에 사용할 경우에 문제점있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 QnA3.ipynb 파일에 테스트 실행 코드를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785400202196&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
question = &quot;대출?&quot;

result_texts = multi_query_retriever.invoke(input=question)
result_texts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 실행 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YfxjC/dJMcaf8uJJe/iwimMkEuOpFuDlMXXwq2AK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YfxjC/dJMcaf8uJJe/iwimMkEuOpFuDlMXXwq2AK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YfxjC/dJMcaf8uJJe/iwimMkEuOpFuDlMXXwq2AK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYfxjC%2FdJMcaf8uJJe%2FiwimMkEuOpFuDlMXXwq2AK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&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;Multi-Query Retriever가 생성한 질문이 영문으로 생성되는 문제점있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INFO:langchain.retrievers.multi_query:Generated&amp;nbsp;queries:&amp;nbsp;['1.&amp;nbsp;What&amp;nbsp;loan&amp;nbsp;options&amp;nbsp;are&amp;nbsp;available&amp;nbsp;for&amp;nbsp;borrowers&amp;nbsp;with&amp;nbsp;my&amp;nbsp;current&amp;nbsp;financial&amp;nbsp;profile?',&amp;nbsp;'2.&amp;nbsp;Could&amp;nbsp;you&amp;nbsp;recommend&amp;nbsp;details&amp;nbsp;on&amp;nbsp;loan&amp;nbsp;terms&amp;nbsp;and&amp;nbsp;interest&amp;nbsp;rates&amp;nbsp;for&amp;nbsp;similar&amp;nbsp;financial&amp;nbsp;situations?',&amp;nbsp;'3.&amp;nbsp;How&amp;nbsp;do&amp;nbsp;different&amp;nbsp;loan&amp;nbsp;products&amp;nbsp;compare&amp;nbsp;in&amp;nbsp;terms&amp;nbsp;of&amp;nbsp;eligibility&amp;nbsp;criteria&amp;nbsp;and&amp;nbsp;repayment&amp;nbsp;options?']&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[번역]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 현재 제 재정 상황에 맞는 대출 옵션은 무엇인가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 비슷한 재정 상황에 맞는 대출 조건과 금리를 자세히 설명해 주시겠어요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;자격&amp;nbsp;기준과&amp;nbsp;상환&amp;nbsp;옵션&amp;nbsp;측면에서&amp;nbsp;다양한&amp;nbsp;대출&amp;nbsp;상품을&amp;nbsp;비교하면&amp;nbsp;어떤가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프롬프트를&amp;nbsp;이용하여&amp;nbsp;한글&amp;nbsp;질문&amp;nbsp;생성하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일에&amp;nbsp;테스트&amp;nbsp;실행&amp;nbsp;코드를&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785402965949&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 프롬프트 (영어)
prompt_template = PromptTemplate.from_template(
&quot;&quot;&quot;You are an AI language model assistant. 
Your task is to generate three different versions of the given user question to retrieve relevant documents from a vector database. 
By generating multiple perspectives on the user question, your goal is to help the user overcome some of the limitations of the distance-based similarity search. 
Your response should be a list of values separated by new lines

#ORIGINAL QUESTION: 
{question}

#Answer in Korean:
&quot;&quot;&quot;
)

question = &quot;대출?&quot;

#chain = prompt | model | StrOutputParser()

#response = chain.invoke({&quot;question&quot;: question})
#print(response)

chain = (
    {&quot;question&quot;: RunnablePassthrough()}
    | prompt_template
    | model
    | StrOutputParser()
)

response = chain.invoke(question)
response&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RunnablePassthrough()&amp;nbsp;는&amp;nbsp;데이터를&amp;nbsp;전달하는&amp;nbsp;역할을&amp;nbsp;합니다.&amp;nbsp;invoke()&amp;nbsp;메서드를&amp;nbsp;통해&amp;nbsp;입력된&amp;nbsp;데이터를&amp;nbsp;그대로&amp;nbsp;반환&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q4LOZ/dJMcagNgqv5/6wGHUdk3qzZmfXGk1KEZvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q4LOZ/dJMcagNgqv5/6wGHUdk3qzZmfXGk1KEZvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q4LOZ/dJMcagNgqv5/6wGHUdk3qzZmfXGk1KEZvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq4LOZ%2FdJMcagNgqv5%2F6wGHUdk3qzZmfXGk1KEZvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트가 생성한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'대출&amp;nbsp;관련&amp;nbsp;정보&amp;nbsp;찾기&amp;nbsp;&amp;nbsp;\n대출&amp;nbsp;조건&amp;nbsp;문의&amp;nbsp;&amp;nbsp;\n금융&amp;nbsp;대출&amp;nbsp;옵션&amp;nbsp;탐색'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트가 한글이면 좀 더 나은 질문을 생성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프롬프트를 한글로 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785403121846&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 프롬프트 (한국어)
prompt_template = PromptTemplate.from_template(
&quot;&quot;&quot;당신은 AI 언어 모델 어시스턴트입니다.
당신의 임무는 주어진 사용자 질문의 세 가지 다른 버전을 생성하여 벡터 데이터베이스에서 관련 문서를 검색하는 것입니다.
사용자 질문에 대한 다양한 관점을 생성함으로써, 사용자가 거리 기반 유사도 검색의 한계를 극복하도록 돕는 것이 당신의 목표입니다.

너의 응답 결과는 줄바꿈으로 구분하여 리스트로 제공합니다.

#원본 질문
{question}
&quot;&quot;&quot;
)

question = &quot;대출?&quot;
chain = (
    {&quot;question&quot;: RunnablePassthrough()}
    | prompt_template
    | model
    | StrOutputParser()
)
response = chain.invoke(question)
response&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;테스트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZKJi1/dJMcaf8uLNF/bm0SVH82GoSWz0lSxo5AA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZKJi1/dJMcaf8uLNF/bm0SVH82GoSWz0lSxo5AA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZKJi1/dJMcaf8uLNF/bm0SVH82GoSWz0lSxo5AA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZKJi1%2FdJMcaf8uLNF%2Fbm0SVH82GoSWz0lSxo5AA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;832&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트가 생성한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'```\n#&amp;nbsp;버전&amp;nbsp;1\n대출&amp;nbsp;조건과&amp;nbsp;이자율에&amp;nbsp;대해&amp;nbsp;자세히&amp;nbsp;알려주시겠어요?\n\n#&amp;nbsp;버전&amp;nbsp;2\n현재&amp;nbsp;이용&amp;nbsp;가능한&amp;nbsp;대출&amp;nbsp;상품의&amp;nbsp;종류와&amp;nbsp;장단점은&amp;nbsp;무엇인가요?\n\n#&amp;nbsp;버전&amp;nbsp;3\n대출&amp;nbsp;신청&amp;nbsp;과정과&amp;nbsp;필요&amp;nbsp;서류는&amp;nbsp;무엇인가요?&amp;nbsp;\n```'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>Advanced RAG</category>
      <category>Exaone3.5</category>
      <category>Pre-Retrieval</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/322</guid>
      <comments>https://carrotweb.tistory.com/322#entry322comment</comments>
      <pubDate>Thu, 30 Jul 2026 18:23:21 +0900</pubDate>
    </item>
    <item>
      <title>LangChain &amp;ndash; Advanced RAG, Pre-Retrieval</title>
      <link>https://carrotweb.tistory.com/321</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 개발한 RAG를 Navie RAG라고 합니다. 지금부터 좀더 업그레이드된 RAG인 Advanced RAG를 개발해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMnZiE/dJMcabkR3Ya/i2Ri9Angx1ZtNfx0FkpXs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMnZiE/dJMcabkR3Ya/i2Ri9Angx1ZtNfx0FkpXs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMnZiE/dJMcabkR3Ya/i2Ri9Angx1ZtNfx0FkpXs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMnZiE%2FdJMcabkR3Ya%2Fi2Ri9Angx1ZtNfx0FkpXs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;768&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;768&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Advanced&amp;nbsp;RAG(Retrieval-Augmented&amp;nbsp;Generation)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Naive RAG의 검색 기능을 개선하고 텍스트 처리 기술을 도입하여 RAG 성능을 향상시킨 모델입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp;Retrieve(검색)&amp;nbsp;품질을&amp;nbsp;개선하는&amp;nbsp;것입니다.&amp;nbsp;검색&amp;nbsp;품질을&amp;nbsp;개선하기&amp;nbsp;위해서&amp;nbsp;검색&amp;nbsp;전(Pre-Retrieval)과&amp;nbsp;검색&amp;nbsp;후(Post-Retrieval)를&amp;nbsp;최적화하는&amp;nbsp;것을&amp;nbsp;의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmANWj/dJMcaafaqsL/3k35N4Frv8RKEGTzPRqBg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmANWj/dJMcaafaqsL/3k35N4Frv8RKEGTzPRqBg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmANWj/dJMcaafaqsL/3k35N4Frv8RKEGTzPRqBg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmANWj%2FdJMcaafaqsL%2F3k35N4Frv8RKEGTzPRqBg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;69&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;90&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;b&gt;Pre-Retrieval(사전 검색) 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색되는 문서(청크)의 질을 높이기 위해 Indexing(인덱싱)를 최적화하고 검색 결과의 질을 높이기 위해 검색 질의(Query)를 재구성(Query Rewrite, Query Expansion)하여 검색을 최적화합니다.&lt;/p&gt;
&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;Post-Retrieval(사후 검색) 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 문서(청크)의 순서를 재정렬하여 Context를 최적화합니다. 처리 방법은 검색된 문서(청크)에 대해 의미론적 관계를 다시 계산하여 순위를 재정렬(Reranking)하고 추출한 다음 압축하여 최적화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;외부&amp;nbsp;지식&amp;rdquo;과&amp;nbsp;&amp;ldquo;모델&amp;nbsp;적응&amp;rdquo;&amp;nbsp;측면에서&amp;nbsp;모델&amp;nbsp;최적화&amp;nbsp;방법&amp;nbsp;비교&lt;/b&gt;&amp;nbsp;(&lt;a href=&quot;https://arxiv.org/pdf/2312.10997&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://arxiv.org/pdf/2312.10997&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXZHLo/dJMcajpuf7k/5YpcFCyS70wIg3vmkiAiJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXZHLo/dJMcajpuf7k/5YpcFCyS70wIg3vmkiAiJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXZHLo/dJMcajpuf7k/5YpcFCyS70wIg3vmkiAiJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXZHLo%2FdJMcajpuf7k%2F5YpcFCyS70wIg3vmkiAiJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;743&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프롬프트 엔지니어링은 모델과 외부 지식 수정을 최소화하며, LLM 자체의 역량을 활용하는데 중점을 둡니다.&lt;/li&gt;
&lt;li&gt;미세 조정은 모델에 대한 추가 학습을 포함합니다.&lt;/li&gt;
&lt;li&gt;Naive/Advanced&amp;nbsp;RAG는&amp;nbsp;모델&amp;nbsp;수정에&amp;nbsp;대한&amp;nbsp;요구가&amp;nbsp;낮으나&amp;nbsp;연구가&amp;nbsp;진행됨에&amp;nbsp;따라&amp;nbsp;Modular&amp;nbsp;RAG는&amp;nbsp;미세&amp;nbsp;조정&amp;nbsp;기술과&amp;nbsp;더욱&amp;nbsp;통합되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Retrieval&amp;nbsp;and&amp;nbsp;Generation&amp;nbsp;(검색과&amp;nbsp;생성)&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;1417&quot; data-origin-height=&quot;727&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byP9xg/dJMcaiKVUv2/mghVypxvQcGZdkjVEQFMuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byP9xg/dJMcaiKVUv2/mghVypxvQcGZdkjVEQFMuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byP9xg/dJMcaiKVUv2/mghVypxvQcGZdkjVEQFMuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyP9xg%2FdJMcaiKVUv2%2FmghVypxvQcGZdkjVEQFMuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;727&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;727&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;&lt;b&gt;Advanced RAG &amp;ndash; Pre-Retrieval (사전 검색)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확하지 않은 질문?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문의&amp;nbsp;의도가&amp;nbsp;정확하지&amp;nbsp;않는&amp;nbsp;질문으로&amp;nbsp;인해&amp;nbsp;정확지&amp;nbsp;않은&amp;nbsp;검색&amp;nbsp;결과가&amp;nbsp;나오게&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vJDmK/dJMb998kE1Q/h2P32fjUZxd0BL5sTLxAIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vJDmK/dJMb998kE1Q/h2P32fjUZxd0BL5sTLxAIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vJDmK/dJMb998kE1Q/h2P32fjUZxd0BL5sTLxAIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvJDmK%2FdJMb998kE1Q%2Fh2P32fjUZxd0BL5sTLxAIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;129&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;162&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;질문이 정확하지 않기 때문에 원하는 결과가 나오지 않을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 대부분의 사용자들은 간단한 질문으로 원하는 결과가 나오길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우문현답(愚問賢答)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(어리석은 질문이라도 현명하게 답변한다)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;개떡같이 말해도 찰떡같이 알아듣는다.&amp;ldquo;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(자세히&amp;nbsp;설명하지&amp;nbsp;않거나,&amp;nbsp;잘못된&amp;nbsp;설명을&amp;nbsp;하여도&amp;nbsp;어떤&amp;nbsp;말인지&amp;nbsp;알아듣는다.)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Query Rewrite (쿼리 재작성)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 입력한 질문을 바로 임베딩하여 벡터 스토어에 검색하지 않고 질문을 기반으로 다양한 관점으로 질문을 재생성하여 검색하게 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;LLM을&amp;nbsp;사용하여&amp;nbsp;여러&amp;nbsp;개의&amp;nbsp;유사&amp;nbsp;질문으로&amp;nbsp;재생성하여&amp;nbsp;단일&amp;nbsp;질문의&amp;nbsp;한계(부정확한&amp;nbsp;질문)를&amp;nbsp;극복하여&amp;nbsp;더&amp;nbsp;좋은&amp;nbsp;검색&amp;nbsp;결과를&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GeXHX/dJMcabyihby/oDuBUf4kgk5c939wCNp2bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GeXHX/dJMcabyihby/oDuBUf4kgk5c939wCNp2bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GeXHX/dJMcabyihby/oDuBUf4kgk5c939wCNp2bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGeXHX%2FdJMcabyihby%2FoDuBUf4kgk5c939wCNp2bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;236&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Multi-Query Retriever&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MultiQueryRetriever는 LLM을 사용하여 주어진 사용자 입력 쿼리에 대해 다른 관점에서 여러 쿼리를 생성함으로써 프롬프트 튜닝 프로세스를 자동화합니다. &lt;br /&gt;각&amp;nbsp;쿼리에&amp;nbsp;대해&amp;nbsp;관련&amp;nbsp;있는&amp;nbsp;문서&amp;nbsp;집합을&amp;nbsp;검색하고&amp;nbsp;모든&amp;nbsp;쿼리에서&amp;nbsp;유닉&amp;nbsp;유니온(UNION과&amp;nbsp;UNION&amp;nbsp;ALL)을&amp;nbsp;사용하여&amp;nbsp;잠재적으로&amp;nbsp;관련이&amp;nbsp;있는&amp;nbsp;더&amp;nbsp;많이&amp;nbsp;문서&amp;nbsp;집합을&amp;nbsp;얻습니다. &lt;br /&gt;MultiQueryRetriever는&amp;nbsp;같은&amp;nbsp;질문에&amp;nbsp;대해&amp;nbsp;여러&amp;nbsp;관점을&amp;nbsp;생성함으로써&amp;nbsp;거리&amp;nbsp;기반&amp;nbsp;검색의&amp;nbsp;한계를&amp;nbsp;완화하고&amp;nbsp;더욱&amp;nbsp;풍부한&amp;nbsp;결과를&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sw2my/dJMcacYjCaM/OggXUe7eYXiWw0z9Xpphv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sw2my/dJMcacYjCaM/OggXUe7eYXiWw0z9Xpphv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sw2my/dJMcacYjCaM/OggXUe7eYXiWw0z9Xpphv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsw2my%2FdJMcacYjCaM%2FOggXUe7eYXiWw0z9Xpphv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA3.ipynb&amp;nbsp;파일을&amp;nbsp;생성하고&amp;nbsp;벡터&amp;nbsp;스토어&amp;nbsp;멀티&amp;nbsp;검색&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5qPQx/dJMcahrQI8z/ATqQ1zFgPIYdJ8x6p7t1W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5qPQx/dJMcahrQI8z/ATqQ1zFgPIYdJ8x6p7t1W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5qPQx/dJMcahrQI8z/ATqQ1zFgPIYdJ8x6p7t1W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5qPQx%2FdJMcahrQI8z%2FATqQ1zFgPIYdJ8x6p7t1W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785398050227&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
벡터 스토어 멀티 검색 (Faiss)

[Code]
import os
from langchain.chat_models import init_chat_model

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

model = init_chat_model(&quot;gpt-4o-mini&quot;, model_provider=&quot;openai&quot;)

[Code]
from langchain_huggingface import HuggingFaceEmbeddings

embeddings_model = HuggingFaceEmbeddings(model_name=&quot;intfloat/multilingual-e5-large-instruct&quot;)

[Code]
from langchain_community.vectorstores import FAISS

loaded_vector_store = FAISS.load_local(
    folder_path=&quot;./faiss_db&quot;, # 저장된 디렉토리
    index_name=&quot;test_db&quot;, # 저장된 인덱스 명
    embeddings=embeddings_model, # 사용할 임베딩 모델
    allow_dangerous_deserialization=True # pickle 파일 역직렬화 허용 
)

[Code]
import logging

logging.basicConfig()
logging.getLogger(&quot;langchain.retrievers.multi_query&quot;).setLevel(logging.INFO)

[Code]
from langchain.retrievers.multi_query import MultiQueryRetriever

multi_query_retriever = MultiQueryRetriever.from_llm(
    retriever=loaded_vector_store.as_retriever(search_kwargs={&quot;k&quot;: 5}),
    llm=model
)

question = &quot;중학생의 수학 기초학력 미달률은?&quot;

result_texts = multi_query_retriever.invoke(input=question)
result_texts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python&amp;nbsp;표준&amp;nbsp;라이브러인&amp;nbsp;logging&amp;nbsp;모듈은&amp;nbsp;프로그램&amp;nbsp;실행&amp;nbsp;중&amp;nbsp;발생하는&amp;nbsp;이벤트들을&amp;nbsp;추적(기록)할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785398389269&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
import logging

logging.basicConfig()
logging.getLogger(&quot;langchain.retrievers.multi_query&quot;).setLevel(logging.INFO)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 모듈의 basicConfig() 함수는 루트 로거를 빠르고 편리하게 구성하는 방법을 제공합니다. 애플리케이션의 기본 로깅 기능 설정을 간소화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 파일에 기록하려면 basicConfig() 함수에 filename 파라미터를 사용하여 로그 파일의 경로를 지정하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;basicConfig()의 기본값 Log Level(로그 레벨)은 logging.WARN 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Logger(로거)는 직접 인스턴스화해서는 안 되고 함수인 logging.getLogger(name)을 통해 인스턴스화해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name 파라미터에 설정된 네임스페이스(패키지(모듈) 계층 구조)에서 발생하는 이벤트들을 추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Log Level(로그 레벨)은 로깅 메시지의 중요도나 심각도를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DEBUG(디버그&amp;nbsp;정보)&amp;nbsp;&amp;gt;&amp;nbsp;INFO(일반&amp;nbsp;정보)&amp;nbsp;&amp;gt;&amp;nbsp;WARNING(경고&amp;nbsp;메시지)&amp;nbsp;&amp;gt;&amp;nbsp;ERROR(에러&amp;nbsp;메시지)&amp;nbsp;&amp;gt;&amp;nbsp;CRITICAL(심각한&amp;nbsp;오류&amp;nbsp;메시지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 벡터&amp;nbsp;스토어&amp;nbsp;멀티&amp;nbsp;검색&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs8H9z/dJMcagTX7f9/mky3l7JYY7pGuaxxFOuNfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs8H9z/dJMcagTX7f9/mky3l7JYY7pGuaxxFOuNfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs8H9z/dJMcagTX7f9/mky3l7JYY7pGuaxxFOuNfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs8H9z%2FdJMcagTX7f9%2Fmky3l7JYY7pGuaxxFOuNfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 쿼리 리트리버가 생성한 질문 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INFO:langchain.retrievers.multi_query:Generated&amp;nbsp;queries:&amp;nbsp;['중학생들의&amp;nbsp;수학&amp;nbsp;기초&amp;nbsp;학력이&amp;nbsp;부족한&amp;nbsp;비율은&amp;nbsp;어떻게&amp;nbsp;되나요?&amp;nbsp;&amp;nbsp;',&amp;nbsp;'중학생을&amp;nbsp;대상으로&amp;nbsp;한&amp;nbsp;수학&amp;nbsp;기초학력&amp;nbsp;미달률에&amp;nbsp;대한&amp;nbsp;통계는&amp;nbsp;무엇이&amp;nbsp;있나요?&amp;nbsp;&amp;nbsp;',&amp;nbsp;'우리나라&amp;nbsp;중학생&amp;nbsp;수학&amp;nbsp;기초학력&amp;nbsp;미달률에&amp;nbsp;관한&amp;nbsp;정보는&amp;nbsp;무엇인가요?']&lt;/p&gt;
&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;[Document(id='fb8315b4-483c-4763-b361-af94b8e80ce0',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='교육부의&amp;nbsp;&amp;lsquo;2023&amp;nbsp;국가수준&amp;nbsp;학업성취도평가&amp;rsquo;에&amp;nbsp;따르면&amp;nbsp;중학생의&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;9.1%,&amp;nbsp;영어&amp;nbsp;6.0%,&amp;nbsp;수학&amp;nbsp;13.0%였으며&amp;nbsp;고등학생은&amp;nbsp;국어&amp;nbsp;8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학'), &lt;br /&gt;&amp;nbsp;Document(id='6dcbfe08-1013-45d4-8d20-fcdbaae62393',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='8.6%,&amp;nbsp;영어&amp;nbsp;8.7%,&amp;nbsp;수학&amp;nbsp;16.6%로&amp;nbsp;집계됐다.&amp;nbsp;특히&amp;nbsp;수학의&amp;nbsp;기초학력&amp;nbsp;부진이&amp;nbsp;가장&amp;nbsp;높아&amp;nbsp;학습&amp;nbsp;격차&amp;nbsp;해소를&amp;nbsp;위한&amp;nbsp;지원이&amp;nbsp;필요한&amp;nbsp;상황이다.'), &lt;br /&gt;&amp;nbsp;Document(id='babccbd5-7557-4c20-85ff-46534ad2b69b',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='기초&amp;nbsp;학력&amp;nbsp;향상에&amp;nbsp;특화된&amp;nbsp;에듀테크&amp;nbsp;솔루션을&amp;nbsp;특별&amp;nbsp;할인가로&amp;nbsp;제공한다.'), &lt;br /&gt;&amp;nbsp;Document(id='241efdba-09b4-4e44-a74b-bcbd645eeb1e',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='테크빌교육&amp;nbsp;체더스,&amp;nbsp;기초학력&amp;nbsp;부진&amp;nbsp;해결을&amp;nbsp;위한&amp;nbsp;코스웨어&amp;nbsp;기획전&amp;nbsp;진행'), &lt;br /&gt;&amp;nbsp;Document(id='32fd32b1-b92d-4dd3-b03a-87d4015ab911',&amp;nbsp;metadata={'source':&amp;nbsp;'data/test.txt'},&amp;nbsp;page_content='솔루션을&amp;nbsp;활용해&amp;nbsp;학생들&amp;nbsp;간의&amp;nbsp;학습&amp;nbsp;격차를&amp;nbsp;해소하는&amp;nbsp;데&amp;nbsp;도움이&amp;nbsp;되길&amp;nbsp;바란다&amp;rdquo;고&amp;nbsp;말했다.')]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>Advanced RAG</category>
      <category>Multi-Query Retriever</category>
      <category>Pre-Retrieval</category>
      <category>Query Rewrite</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/321</guid>
      <comments>https://carrotweb.tistory.com/321#entry321comment</comments>
      <pubDate>Thu, 30 Jul 2026 17:07:25 +0900</pubDate>
    </item>
    <item>
      <title>랭체인 - Chat Prompt Template, LCEL(LangChain Expression Language)</title>
      <link>https://carrotweb.tistory.com/320</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prompt(프롬프트)란&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 대규모(초거대)&amp;nbsp;언어&amp;nbsp;모델(LLM)에게 질문이나 작업을 요청할때 사용되는 입력문입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Prompt Template(프롬프트 템플릿)이란&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PromptTemplate를 사용하여 프롬프트(입력문)안에 변수를 포함하여 생성한 후 동적으로 입력받은 변수 값으로 채워 넣어 프롬프트를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785054434973&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_core.prompts import PromptTemplate

# 'role' 변수를 사용하는 프롬프트 템플릿을 정의
template = &quot;너는 {role} 전문가로 {role} 질문에 친절하게 답변해 주십시오.&quot;

# PromptTemplate 인스턴스를 생성
promptTemplate = PromptTemplate.from_template(template)

# 템플릿에 변수 값을 채워 넣어 프롬프트를 완성
prompt = promptTemplate.format(role=&quot;천문학&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1785054465401&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;너는 천문학 전문가로 천문학 질문에 친절하게 답변해 주십시오.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Chat&amp;nbsp;Prompt&amp;nbsp;Template(챗&amp;nbsp;프롬프트&amp;nbsp;템플릿)이란&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatPromptTemplate를 사용하여 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;LangChain Message인&amp;nbsp;SystemMessage와 HumanMessage에 변수를 포함하여 생성한 후 동적으로 입력받은 변수 값으로 채워 넣어 프롬프트를 생성합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://python.langchain.com/docs/tutorials/llm_chain/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/tutorials/llm_chain/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9ds3A/dJMcaasFigM/T1JDP5RIjKA9fnbiNFPjbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9ds3A/dJMcaasFigM/T1JDP5RIjKA9fnbiNFPjbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9ds3A/dJMcaasFigM/T1JDP5RIjKA9fnbiNFPjbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9ds3A%2FdJMcaasFigM%2FT1JDP5RIjKA9fnbiNFPjbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Iz2XM/dJMcaf1IsfV/8DUtSPpinHeWpXChCAdsIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Iz2XM/dJMcaf1IsfV/8DUtSPpinHeWpXChCAdsIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Iz2XM/dJMcaf1IsfV/8DUtSPpinHeWpXChCAdsIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIz2XM%2FdJMcaf1IsfV%2F8DUtSPpinHeWpXChCAdsIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Chat Prompt Template 사용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;qna.py&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn2qJJ/dJMcabLVJ46/hK3fsN0uTkVpJFGAyDdbK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn2qJJ/dJMcabLVJ46/hK3fsN0uTkVpJFGAyDdbK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn2qJJ/dJMcabLVJ46/hK3fsN0uTkVpJFGAyDdbK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn2qJJ%2FdJMcabLVJ46%2FhK3fsN0uTkVpJFGAyDdbK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785055280330&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

model = init_chat_model(&quot;gpt-4o&quot;, model_provider=&quot;openai&quot;)

system_template = &quot;너는 천문학 전문가로 천문학 질문에 친절하게 답변해 주십시오.&quot;

prompt_template = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, system_template),
    (&quot;user&quot;, &quot;{text}&quot;)
])

prompt = prompt_template.invoke({&quot;text&quot;: &quot;태양계에서 가장 큰 행성은 무엇인가요?&quot;})

ai_msg = model.invoke(prompt)
print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terminal(터미널)에서 명령어를 사용하여 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJFmj/dJMb998iiKg/MYYQDgK9NbA97YV6UcEVF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJFmj/dJMb998iiKg/MYYQDgK9NbA97YV6UcEVF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJFmj/dJMb998iiKg/MYYQDgK9NbA97YV6UcEVF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJFmj%2FdJMb998iiKg%2FMYYQDgK9NbA97YV6UcEVF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 출력 결과 : 태양계에서 가장 큰 행성은 목성입니다. 목성은 직경과 질량 모두에서 다른 행성들보 다 훨씬 크며, 태양계 전체 행성 질량의 상당 부분을 차지하고 있습니다. 목성은 주로 수소와 헬륨으로 이루어진 가스 행성이며, 그 방대한 크기와 대기 조건 덕분에 여러 독특한 특징을 가지고 있습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;chain으로 실행하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StrOutputParser&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StrOutputParser는 모델의 출력 결과를(AIMessage 또는 AIMessageChunk)을 일반 텍스트 문자열로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 출력 결과가 ChatModel 메시지일 경우 StrOutputParser는 메시지의 .content 속성을 추출하여 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://reference.langchain.com/python/langchain-core/output_parsers/string/StrOutputParser&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://reference.langchain.com/python/langchain-core/output_parsers/string/StrOutputParser&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L0Slb/dJMcadCJ3lb/tOIAzc9aCem1zSpFDTzxr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L0Slb/dJMcadCJ3lb/tOIAzc9aCem1zSpFDTzxr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L0Slb/dJMcadCJ3lb/tOIAzc9aCem1zSpFDTzxr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL0Slb%2FdJMcadCJ3lb%2FtOIAzc9aCem1zSpFDTzxr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LangChain의 LCEL(LangChain Expression Language)을 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트, 언어 모델, 출력 파서를 파이프 기호(|)로 연결해 하나의 실행 가능한 작업 흐름(chain)을 만드는 구문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1785056480986&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chain = prompt_template | model | StrOutputParser()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;qna.py&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Bi6J/dJMcadXbpsZ/nhbawFq298V3b3s3xfc27k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Bi6J/dJMcadXbpsZ/nhbawFq298V3b3s3xfc27k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Bi6J/dJMcadXbpsZ/nhbawFq298V3b3s3xfc27k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Bi6J%2FdJMcadXbpsZ%2FnhbawFq298V3b3s3xfc27k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785056173235&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

model = init_chat_model(&quot;gpt-4o&quot;, model_provider=&quot;openai&quot;)

system_template = &quot;너는 천문학 전문가로 천문학 질문에 친절하게 답변해 주십시오.&quot;

prompt_template = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, system_template),
    (&quot;user&quot;, &quot;{text}&quot;)
])

chain = prompt_template | model | StrOutputParser()
response = chain.invoke({&quot;text&quot;: &quot;태양계에서 가장 큰 행성은 무엇인가요?&quot;})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terminal(터미널)에서 명령어를 사용하여 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b47HLC/dJMcacxdtWe/veZiIii5PmrqTJxsjIsyK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b47HLC/dJMcacxdtWe/veZiIii5PmrqTJxsjIsyK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b47HLC/dJMcacxdtWe/veZiIii5PmrqTJxsjIsyK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb47HLC%2FdJMcacxdtWe%2FveZiIii5PmrqTJxsjIsyK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&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;</description>
      <category>LLM Application/LangChain</category>
      <category>chain</category>
      <category>Chat Prompt Template</category>
      <category>LCEL</category>
      <category>stroutputparser</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/320</guid>
      <comments>https://carrotweb.tistory.com/320#entry320comment</comments>
      <pubDate>Sun, 26 Jul 2026 18:04:56 +0900</pubDate>
    </item>
    <item>
      <title>랭체인 - LangChain Message (SystemMessage, HumanMessage, AIMessage)</title>
      <link>https://carrotweb.tistory.com/319</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain Message에 대해 알아보겠습니다.&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;1185&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DJ9Cv/dJMcahL1m1J/EwcJ6iaMkNKUt0Aloux4xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DJ9Cv/dJMcahL1m1J/EwcJ6iaMkNKUt0Aloux4xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DJ9Cv/dJMcahL1m1J/EwcJ6iaMkNKUt0Aloux4xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDJ9Cv%2FdJMcahL1m1J%2FEwcJ6iaMkNKUt0Aloux4xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1185&quot; height=&quot;666&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화 메시지를 Role(역활)로 구분해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QpM9w/dJMcaf8r3WW/Tkjam1anK9TtyWMl3LveS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QpM9w/dJMcaf8r3WW/Tkjam1anK9TtyWMl3LveS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QpM9w/dJMcaf8r3WW/Tkjam1anK9TtyWMl3LveS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQpM9w%2FdJMcaf8r3WW%2FTkjam1anK9TtyWMl3LveS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1326&quot; height=&quot;875&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;875&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;첫번쩨 메시지는 Developer(개발자)가 LLM에 역화과 작업을 설명 &amp;rarr; Task Description (작업 설명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 메시지는 User(사용자)가 LLM에게 질문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째 메시지는 Assistant(어시스턴트)가 User(사용자)의 질문에 대한 대답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화 시작전에 Task Description (작업 설명)이 설명되어 있으면 LLM으로부터 좀더 정확한 결과를 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화 메시지를 LangChain Message로 구분해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1467&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DYhfE/dJMcai5aNk2/Kte7MgBPTUbLSgvaQbVFK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DYhfE/dJMcai5aNk2/Kte7MgBPTUbLSgvaQbVFK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DYhfE/dJMcai5aNk2/Kte7MgBPTUbLSgvaQbVFK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDYhfE%2FdJMcai5aNk2%2FKte7MgBPTUbLSgvaQbVFK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1467&quot; height=&quot;875&quot; data-origin-width=&quot;1467&quot; data-origin-height=&quot;875&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;SystemMessage&amp;nbsp;:&amp;nbsp;시스템&amp;nbsp;메시지로&amp;nbsp;AI의&amp;nbsp;행동(역할이나&amp;nbsp;정보를&amp;nbsp;통한&amp;nbsp;설정)을&amp;nbsp;촉진하기&amp;nbsp;위한&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;1299&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csLHlQ/dJMcajbWuUy/XuzYfsFWIeHB0qp5NiKj00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csLHlQ/dJMcajbWuUy/XuzYfsFWIeHB0qp5NiKj00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csLHlQ/dJMcajbWuUy/XuzYfsFWIeHB0qp5NiKj00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsLHlQ%2FdJMcajbWuUy%2FXuzYfsFWIeHB0qp5NiKj00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;866&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&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;HumanMessage&amp;nbsp;:&amp;nbsp;인간&amp;nbsp;메시지로&amp;nbsp;AI&amp;nbsp;모델에&amp;nbsp;전달할&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;1299&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNbWos/dJMcadXaYhH/kelqm4U36ohqAKStM1INA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNbWos/dJMcadXaYhH/kelqm4U36ohqAKStM1INA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNbWos/dJMcadXaYhH/kelqm4U36ohqAKStM1INA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNbWos%2FdJMcadXaYhH%2Fkelqm4U36ohqAKStM1INA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;866&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&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;AIMessage&amp;nbsp;:&amp;nbsp;AI&amp;nbsp;메시지로&amp;nbsp;프롬프트에&amp;nbsp;대한&amp;nbsp;응답&amp;nbsp;메시지입니다.&amp;nbsp;(채팅&amp;nbsp;모델에서&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;1299&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHt6Pk/dJMcabLVlKu/ZB5EWbJtTxBkWQ8k50MYTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHt6Pk/dJMcabLVlKu/ZB5EWbJtTxBkWQ8k50MYTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHt6Pk/dJMcabLVlKu/ZB5EWbJtTxBkWQ8k50MYTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHt6Pk%2FdJMcabLVlKu%2FZB5EWbJtTxBkWQ8k50MYTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;866&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LangChain Message로 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랭체인&amp;nbsp;사이트의&amp;nbsp;튜토리얼을&amp;nbsp;기반으로&amp;nbsp;개발하도록&amp;nbsp;하겠습니다. &lt;br /&gt;작성&amp;nbsp;당시&amp;nbsp;버전은&amp;nbsp;v0.3이였습니다.&amp;nbsp;현재&amp;nbsp;v1.3과&amp;nbsp;다를&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAt1G7/dJMcacxc36M/SfJ4O35DIiABbMUsZiuPV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAt1G7/dJMcacxc36M/SfJ4O35DIiABbMUsZiuPV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAt1G7/dJMcacxc36M/SfJ4O35DIiABbMUsZiuPV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAt1G7%2FdJMcacxc36M%2FSfJ4O35DIiABbMUsZiuPV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;main.py&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPpVT5/dJMcagsPeW8/r7b9AfGtPczBEnKkRX5q2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPpVT5/dJMcagsPeW8/r7b9AfGtPczBEnKkRX5q2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPpVT5/dJMcagsPeW8/r7b9AfGtPczBEnKkRX5q2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPpVT5%2FdJMcagsPeW8%2Fr7b9AfGtPczBEnKkRX5q2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784970797961&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

model = init_chat_model(&quot;gpt-4o&quot;, model_provider=&quot;openai&quot;)

messages = [
    SystemMessage(
        content=&quot;You are a helpful assistant! Your name is Bob.&quot;
    ),
    HumanMessage(
        content=&quot;What is your name?&quot;
    )
]

ai_msg = model.invoke(messages)
print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 OpenAI 모델만 전용으로 클래스인 ChatOpenAI를 사용햐도 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFyDZW/dJMcadphizZ/oA1ZxZxCvDwXDzZKhxYWZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFyDZW/dJMcadphizZ/oA1ZxZxCvDwXDzZKhxYWZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFyDZW/dJMcadphizZ/oA1ZxZxCvDwXDzZKhxYWZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFyDZW%2FdJMcadphizZ%2FoA1ZxZxCvDwXDzZKhxYWZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784971226002&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

llm = ChatOpenAI(
    model=&quot;gpt-4o&quot;
)

messages = [
    SystemMessage(
        content=&quot;You are a helpful assistant! Your name is Bob.&quot;
    ),
    HumanMessage(
        content=&quot;What is your name?&quot;
    )
]

ai_msg = llm.invoke(messages)
print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화 메시지 처럼 역활과 작업 설명을 해주기 위해서 SystemMessage에 &quot;You are a helpful assistant! Your name is Bob.&quot;을 입력하고 사용자의 질문을 위해 HumanMessage에 &quot;What is your name?&quot;을 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하기&amp;nbsp;위해&amp;nbsp;Terminal(터미널)에서&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;명령어를&amp;nbsp;실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784971038963&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python main.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMPlse/dJMcab58jRj/V7gKvoHRclxQKhIy03ERVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMPlse/dJMcab58jRj/V7gKvoHRclxQKhIy03ERVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMPlse/dJMcab58jRj/V7gKvoHRclxQKhIy03ERVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMPlse%2FdJMcab58jRj%2FV7gKvoHRclxQKhIy03ERVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;결과를 AIMessage로 &quot;My name is Bob. How can I assist you today?&quot;가 전달됩니다.&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>AIMessage</category>
      <category>HumanMessage</category>
      <category>SystemMessage</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/319</guid>
      <comments>https://carrotweb.tistory.com/319#entry319comment</comments>
      <pubDate>Sun, 26 Jul 2026 16:58:14 +0900</pubDate>
    </item>
    <item>
      <title>LangChain - RAG + Ollama + Hugging Face - llama3-2-Korean</title>
      <link>https://carrotweb.tistory.com/318</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Hugging Face 모델을 다운로드하여 Ollama에서 사용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어가 포함되어 있는 llama-3.2-Korean Models를 사용해보겠습니다.&lt;br /&gt;(&lt;a href=&quot;https://huggingface.co/Bllossom/llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/Bllossom/llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg7RMg/dJMcaiqCf6s/bL7I7T1REeauBKpKkAwTd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg7RMg/dJMcaiqCf6s/bL7I7T1REeauBKpKkAwTd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg7RMg/dJMcaiqCf6s/bL7I7T1REeauBKpKkAwTd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg7RMg%2FdJMcaiqCf6s%2FbL7I7T1REeauBKpKkAwTd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. llama-3.2-Korean Models을 다운로드합니다.&lt;br /&gt;(&lt;a href=&quot;https://huggingface.co/Bllossom/llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M/tree/main)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/Bllossom/llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M/tree/main)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HmadS/dJMcafgqGT1/6aTjmBhw9EKhKzVv01oQ0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HmadS/dJMcafgqGT1/6aTjmBhw9EKhKzVv01oQ0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HmadS/dJMcafgqGT1/6aTjmBhw9EKhKzVv01oQ0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHmadS%2FdJMcafgqGT1%2F6aTjmBhw9EKhKzVv01oQ0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M.gguf&amp;nbsp;파일을&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;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8JZwr/dJMcaf8rKuh/w4cMH9OZuX48UNoB1kUL1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8JZwr/dJMcaf8rKuh/w4cMH9OZuX48UNoB1kUL1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8JZwr/dJMcaf8rKuh/w4cMH9OZuX48UNoB1kUL1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8JZwr%2FdJMcaf8rKuh%2Fw4cMH9OZuX48UNoB1kUL1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GGUF : GPT(generative pre-trained transformer) Unified Format&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 콘솔에서 ollama create 명령어를 사용하여 llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M를 Ollama에서 사용할 수 있게 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784905792206&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama create llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bahL59/dJMcah6pJil/G8MzdgBEgFUKSMKGZaDvBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bahL59/dJMcah6pJil/G8MzdgBEgFUKSMKGZaDvBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bahL59/dJMcah6pJil/G8MzdgBEgFUKSMKGZaDvBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbahL59%2FdJMcah6pJil%2FG8MzdgBEgFUKSMKGZaDvBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;708&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델을 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ollama 엡&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서 실행하지 않기 때문에 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Modelfile&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;은&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성하지 않았습니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약, Ollama 앱에서 사용할 경우에는 다운로드 폴더에 Modelfile 파일을 생성해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784906019504&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M.gguf

TEMPLATE &quot;&quot;&quot;{{- if .System }}
&amp;lt;s&amp;gt;{{ .System }}&amp;lt;/s&amp;gt;
{{- end }}
&amp;lt;s&amp;gt;Human:
{{ .Prompt }}&amp;lt;/s&amp;gt;
&amp;lt;s&amp;gt;Assistant:
&quot;&quot;&quot;

SYSTEM &quot;&quot;&quot;A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.&quot;&quot;&quot;

PARAMETER stop &amp;lt;s&amp;gt;
PARAMETER stop &amp;lt;/s&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다운로드 폴더에서 콘설을 실행하여 ollama create 명령어에 Modelfile을 포함하여 실행하면 됩니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784906058752&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama create llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M -f Modelfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Generation (생성) &amp;ndash; LLM (답변 생성)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일에&amp;nbsp;Ollama로&amp;nbsp;프롬프트&amp;nbsp;실행&amp;nbsp;코드를&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784906140826&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser

model = ChatOllama(
    model=&quot;llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M&quot;,
    temperature=0
)

texts = &quot;\n\n&quot;.join(result_text.page_content for result_text in result_texts)

chain = prompt | model | StrOutputParser()
response = chain.invoke({&quot;context&quot; : texts, &quot;question&quot;: &quot;중학생의 수학 기초학력 미달률은?&quot;})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Ollama 프롬프트 실행 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG0PTP/dJMcahSI3YG/wP87W8gNJc2ip1rPuK5l8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG0PTP/dJMcahSI3YG/wP87W8gNJc2ip1rPuK5l8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG0PTP/dJMcahSI3YG/wP87W8gNJc2ip1rPuK5l8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG0PTP%2FdJMcahSI3YG%2FwP87W8gNJc2ip1rPuK5l8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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;Ollama&amp;nbsp;프롬프트&amp;nbsp;실행&amp;nbsp;결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중학생의&amp;nbsp;수학&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;13.0%입니다.&amp;nbsp;이는&amp;nbsp;고등학생&amp;nbsp;수학&amp;nbsp;기초학력&amp;nbsp;미달률(16.6%)보다&amp;nbsp;낮습니다.&amp;nbsp;수학&amp;nbsp;기초학력&amp;nbsp;부진이&amp;nbsp;가장&amp;nbsp;높아&amp;nbsp;학습&amp;nbsp;격차&amp;nbsp;해소를&amp;nbsp;위한&amp;nbsp;지원이&amp;nbsp;필요한&amp;nbsp;상황입니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;Note:&amp;nbsp;The&amp;nbsp;answer&amp;nbsp;is&amp;nbsp;based&amp;nbsp;on&amp;nbsp;the&amp;nbsp;provided&amp;nbsp;context,&amp;nbsp;which&amp;nbsp;mentions&amp;nbsp;that&amp;nbsp;13.0%&amp;nbsp;of&amp;nbsp;middle&amp;nbsp;school&amp;nbsp;students&amp;nbsp;have&amp;nbsp;a&amp;nbsp;low&amp;nbsp;basic&amp;nbsp;math&amp;nbsp;level.&amp;nbsp;(참고:&amp;nbsp;답변은&amp;nbsp;제공된&amp;nbsp;맥락을&amp;nbsp;기반으로&amp;nbsp;하며,&amp;nbsp;중학교&amp;nbsp;학생의&amp;nbsp;13.0%가&amp;nbsp;기초&amp;nbsp;수학&amp;nbsp;수준이&amp;nbsp;낮다는&amp;nbsp;내용을&amp;nbsp;담고&amp;nbsp;있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context가&amp;nbsp;없을&amp;nbsp;경우&amp;nbsp;실행&amp;nbsp;결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중학생의&amp;nbsp;수학&amp;nbsp;기초학력&amp;nbsp;미달률은&amp;nbsp;2019년&amp;nbsp;기준으로&amp;nbsp;약&amp;nbsp;10%에&amp;nbsp;불과하다.&amp;nbsp;이는&amp;nbsp;교육부가&amp;nbsp;발표한&amp;nbsp;데이터를&amp;nbsp;바탕으로&amp;nbsp;한&amp;nbsp;것으로,&amp;nbsp;이&amp;nbsp;데이터는&amp;nbsp;2018년부터&amp;nbsp;2020년까지의&amp;nbsp;수학&amp;nbsp;기초학력을&amp;nbsp;평가한&amp;nbsp;결과다.&amp;nbsp;이&amp;nbsp;미달률은&amp;nbsp;학생들이&amp;nbsp;수학&amp;nbsp;기초&amp;nbsp;학습을&amp;nbsp;충분히&amp;nbsp;하지&amp;nbsp;못하고&amp;nbsp;있는&amp;nbsp;것을&amp;nbsp;나타낸다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;Note:&amp;nbsp;The&amp;nbsp;answer&amp;nbsp;is&amp;nbsp;based&amp;nbsp;on&amp;nbsp;the&amp;nbsp;provided&amp;nbsp;context&amp;nbsp;and&amp;nbsp;may&amp;nbsp;not&amp;nbsp;be&amp;nbsp;up-to-date&amp;nbsp;or&amp;nbsp;accurate&amp;nbsp;for&amp;nbsp;current&amp;nbsp;information.&amp;nbsp; &lt;br /&gt;If&amp;nbsp;you&amp;nbsp;need&amp;nbsp;more&amp;nbsp;recent&amp;nbsp;data,&amp;nbsp;please&amp;nbsp;let&amp;nbsp;me&amp;nbsp;know.&amp;nbsp;(참고:&amp;nbsp;답변은&amp;nbsp;제공된&amp;nbsp;맥락을&amp;nbsp;기반으로&amp;nbsp;하며,&amp;nbsp;최신&amp;nbsp;정보와&amp;nbsp;일치하지&amp;nbsp;않거나&amp;nbsp;정확하지&amp;nbsp;않을&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;더&amp;nbsp;최신&amp;nbsp;데이터가&amp;nbsp;필요하시면&amp;nbsp;알려주세요.)&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>hugging face</category>
      <category>llama-3.2-Korean</category>
      <category>Ollama</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/318</guid>
      <comments>https://carrotweb.tistory.com/318#entry318comment</comments>
      <pubDate>Sat, 25 Jul 2026 00:24:24 +0900</pubDate>
    </item>
    <item>
      <title>LangChain - RAG + Ollama + gemma3</title>
      <link>https://carrotweb.tistory.com/317</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;LangChain에서 &lt;/span&gt;Ollama 연동하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain에서&amp;nbsp;Ollama와 연동하는 방법을 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/providers/ollama&quot;&gt;https://docs.langchain.com/oss/python/integrations/providers/ollama&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boubq9/dJMcaijSro9/5q1GoqkopAhlxIobGu6r41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boubq9/dJMcaijSro9/5q1GoqkopAhlxIobGu6r41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boubq9/dJMcaijSro9/5q1GoqkopAhlxIobGu6r41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fboubq9%2FdJMcaijSro9%2F5q1GoqkopAhlxIobGu6r41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;Ollama를 사용하면 Llama3.1과 같은 오픈 소스 대규모 언어 모델을 로컬에서 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ollama는 모델 가중치, 구성 그리고 데이터를 Modelfile로 정의된 단일 패키지로 묶습니다. GPU 사용량을 포함한 설정 및 구성 세부 정보를 최적화합니다. 지원되는 모델 및 모델 변형의 전체 목록은 Ollama 모델 라이브러리를 참조하세요.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OllamaLLM은 LangChain에서 Ollama 모델과 상호 작용하는 방법을 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/llms/ollama&quot;&gt;https://docs.langchain.com/oss/python/integrations/llms/ollama&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu3InF/dJMb99NVlEh/ZQJU3pbCQ4UJT3K5DwMU11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu3InF/dJMb99NVlEh/ZQJU3pbCQ4UJT3K5DwMU11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu3InF/dJMb99NVlEh/ZQJU3pbCQ4UJT3K5DwMU11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu3InF%2FdJMb99NVlEh%2FZQJU3pbCQ4UJT3K5DwMU11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;b&gt;Ollama 실행하기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. project3&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;QnA2.ipynb&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 파일에&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Ollama&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 코드를 추가합니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnxnBE/dJMcafOiPRy/466D20LR83fBTkjG4MZgOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnxnBE/dJMcafOiPRy/466D20LR83fBTkjG4MZgOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnxnBE/dJMcafOiPRy/466D20LR83fBTkjG4MZgOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnxnBE%2FdJMcafOiPRy%2F466D20LR83fBTkjG4MZgOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784883020385&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
Ollama Gemma3:4b

[Code]
from langchain_community.llms import Ollama
from langchain_core.prompts import ChatPromptTemplate

model = Ollama(model=&quot;gemma3:1b&quot;)

system_template = &quot;너는 천문학 전문가로 천문학 질문에 친절하게 답변해 주십시오.&quot;

prompt_template = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, system_template),
    (&quot;user&quot;, &quot;{text}&quot;)
])

prompt = prompt_template.invoke({&quot;text&quot;: &quot;태양계에서 가장 큰 행성은 무엇인가요?&quot;})

response = model.invoke(prompt)
response&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Ollama&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GjItb/dJMcaiEb0kb/PV8nl7ds3kZNnVgDvIzHr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GjItb/dJMcaiEb0kb/PV8nl7ds3kZNnVgDvIzHr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GjItb/dJMcaiEb0kb/PV8nl7ds3kZNnVgDvIzHr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGjItb%2FdJMcaiEb0kb%2FPV8nl7ds3kZNnVgDvIzHr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;Ollama 코드 실행 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883087375&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'안녕하세요! 천문학 전문가로서 태양계 행성에서 가장 큰 행성은 **목성**입니다. \n\n목성은 태양을 중심으로 하는 역전 운동 시스템의 중력으로 인해 엄청난 크기를 가지고 있습니다. 이는 태양의 중력에 비해 훨씬 크기 때문에, 목성은 태양계의 가장 큰 행성이 됩니다.\n\n혹시 더 궁금한 점이 있으신가요? 예를 들어, 목성의 특징이나 행성 간의 상대적인 크기 등에 대해 더 자세히 알려드릴 수 있습니다.'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&lt;b&gt;Generation&amp;nbsp;(생성)&amp;nbsp;&amp;ndash;&amp;nbsp;LLM&amp;nbsp;(답변&amp;nbsp;생성)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일에&amp;nbsp;Ollama로&amp;nbsp;프롬프트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b91mqb/dJMcacqtXuD/UG1SkRpYWFeYbtAgrJVze1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b91mqb/dJMcacqtXuD/UG1SkRpYWFeYbtAgrJVze1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b91mqb/dJMcacqtXuD/UG1SkRpYWFeYbtAgrJVze1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb91mqb%2FdJMcacqtXuD%2FUG1SkRpYWFeYbtAgrJVze1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784883260131&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
%pip install langchain-ollama

[Code]
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = ChatOllama(
    model=&quot;gemma3:1b&quot;,
    temperature=0
)

texts = &quot;\n\n&quot;.join(result_text.page_content for result_text in result_texts)

chain = prompt | model | StrOutputParser()
response = chain.invoke({&quot;context&quot; : texts, &quot;question&quot;: &quot;중학생의 수학 기초학력 미달률은?&quot;})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식은 chain 방식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883318866&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;do_prompt = prompt.invoke({&quot;context&quot; : texts, &quot;question&quot;: &quot;중학생의 수학 기초학력 미달률은?&quot;})

ai_msg = model.invoke(do_prompt)
print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 첫번째 [Code]를 실행하여 langchain-ollama를&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;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EYAup/dJMcaiKSBXL/Nkn0l5uoJISHMfyWgTVuFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EYAup/dJMcaiKSBXL/Nkn0l5uoJISHMfyWgTVuFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EYAup/dJMcaiKSBXL/Nkn0l5uoJISHMfyWgTVuFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEYAup%2FdJMcaiKSBXL%2FNkn0l5uoJISHMfyWgTVuFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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;3. Ollama로&amp;nbsp;프롬프트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8atk4/dJMcaiKSBYa/HCJy6KBS4rKFTQDvRMyAt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8atk4/dJMcaiKSBYa/HCJy6KBS4rKFTQDvRMyAt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8atk4/dJMcaiKSBYa/HCJy6KBS4rKFTQDvRMyAt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8atk4%2FdJMcaiKSBYa%2FHCJy6KBS4rKFTQDvRMyAt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;Ollama 프롬프트 실행 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883492293&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;The 기초학력 미달률은 9.1%입니다. 특히 수학의 기초학력 부진이 가장 높습니다. 테크빌교육은 기초학력 향상 코스웨어 기획전을 진행하고 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context가 없을 경우 실행 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883525916&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;The provided context does not contain information about the math 기초학력 미달률. (제공된 컨텍스트에는 수학 기초학력 미달률에 대한 정보가 포함되어 있지 않습니다.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ollama에서 gemma3n 다운로드 하기&lt;br /&gt;1. Ollama Models Search (&lt;a href=&quot;https://ollama.com/library/gemma3n&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com/library/gemma3n&lt;/a&gt;)에서 gemma3n를 검색합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYyVZv/dJMcabrwo35/hxXZaSceMtiqH6q9I6wNoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYyVZv/dJMcabrwo35/hxXZaSceMtiqH6q9I6wNoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYyVZv/dJMcabrwo35/hxXZaSceMtiqH6q9I6wNoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYyVZv%2FdJMcabrwo35%2FhxXZaSceMtiqH6q9I6wNoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 콘솔에서 ollama pull 명령어를 사용하여 gemma3n:e4b를 다운로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883729169&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama pull gemma3n:e4b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R2wJM/dJMcabE0yV0/ynsciQRaFrELM6uLWI2Iy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R2wJM/dJMcabE0yV0/ynsciQRaFrELM6uLWI2Iy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R2wJM/dJMcabE0yV0/ynsciQRaFrELM6uLWI2Iy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR2wJM%2FdJMcabE0yV0%2FynsciQRaFrELM6uLWI2Iy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;708&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&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;3. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일에&amp;nbsp;Ollama로&amp;nbsp;프롬프트&amp;nbsp;실행&amp;nbsp;코드를&amp;nbsp;수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883805759&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser

model = ChatOllama(
    model=&quot;gemma3n:e4b&quot;,
    temperature=0
)

texts = &quot;\n\n&quot;.join(result_text.page_content for result_text in result_texts)

chain = prompt | model | StrOutputParser()
response = chain.invoke({&quot;context&quot; : texts, &quot;question&quot;: &quot;중학생의 수학 기초학력 미달률은?&quot;})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Ollama로 프롬프트 실행 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU8SO7/dJMcafOiQOf/vZntxHGJm0yElvdYLd6bl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU8SO7/dJMcafOiQOf/vZntxHGJm0yElvdYLd6bl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU8SO7/dJMcafOiQOf/vZntxHGJm0yElvdYLd6bl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU8SO7%2FdJMcafOiQOf%2FvZntxHGJm0yElvdYLd6bl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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;Ollama 프롬프트 실행 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883898401&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;교육부의 '2023 국가수준 학업성취도평가' 결과, 중학생의 수학 기초학력 미달률은 13.0%로 나타났습니다. 이는 영어 6.0%보다 높은 수치이며, 특히 수학의 기초학력 부진이 심각한 상황입니다. 따라서 학습 격차 해소를 위한 지원이 필요합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context가 없을 경우 실행 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784883911354&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I am sorry, but I cannot answer the question about the percentage of middle school students with insufficient basic math skills. The provided context is empty, so I do not have the information needed to answer. Therefore, I don't know the answer. (죄송하지만, 중학생 중 기초 수학 능력이 부족한 학생의 비율에 대한 질문에는 답변해 드릴 수 없습니다. 제공된 정보가 부족하여 답변에 필요한 정보를 얻을 수 없습니다. 따라서 답변을 드릴 수 없습니다.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>ChatOllama</category>
      <category>chatprompttemplate</category>
      <category>gemma3</category>
      <category>OllamaLLM</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/317</guid>
      <comments>https://carrotweb.tistory.com/317#entry317comment</comments>
      <pubDate>Fri, 24 Jul 2026 18:06:46 +0900</pubDate>
    </item>
    <item>
      <title>Open Source (Local) LLM - Ollama 사용하기</title>
      <link>https://carrotweb.tistory.com/316</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Open&amp;nbsp;Source&amp;nbsp;(Local)&amp;nbsp;LLM&amp;nbsp;장점과&amp;nbsp;단점&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안성 : 유료 LLM(대규모(초거대) 언어 모델)를 사용하지 않기 때문에 데이터가 유출될 위험이 없습니다.&lt;/li&gt;
&lt;li&gt;접근성 : 내부 망에 서버가 있어 외부 망과 연결하지 않아도 되고 인터넷 연결 없이도 사용이 가능합니다.&lt;/li&gt;
&lt;li&gt;빠른 응답 : 내부 서버에서 실행되므로 처리 응답이 빠릅니다. (대기 시간이 짧아집니다.)&lt;/li&gt;
&lt;li&gt;비용 절감 : 유료 LLM 사용료가 없습니다.&lt;/li&gt;
&lt;li&gt;다양한&amp;nbsp;모델&amp;nbsp;사용&amp;nbsp;:&amp;nbsp;서버나&amp;nbsp;서비스에&amp;nbsp;맞게&amp;nbsp;다양한&amp;nbsp;모델을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확성의 한계 : 모델이 학습된 날짜가 오래될 경우 오래된 정보가 제공되거나 정보가 없을 수도 있습니다. (파인 튜닝 또는 관련 정보 별도 구축 후 연동)&lt;/li&gt;
&lt;li&gt;높은&amp;nbsp;시스템&amp;nbsp;비용&amp;nbsp;:&amp;nbsp;모델에&amp;nbsp;따라&amp;nbsp;고성능&amp;nbsp;GPU와&amp;nbsp;대용량&amp;nbsp;메모리가&amp;nbsp;있는&amp;nbsp;시스템을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;서버&amp;nbsp;비용&amp;nbsp;부담이&amp;nbsp;증가될&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유료 LLM(대규모(초거대) 언어 모델)을 대처할 Open Source LLM(대규모(초거대) 언어 모델) 사용&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;469&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9wbbI/dJMcaasD8zc/9KN6KNDP79TQAIUpvPRwM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9wbbI/dJMcaasD8zc/9KN6KNDP79TQAIUpvPRwM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9wbbI/dJMcaasD8zc/9KN6KNDP79TQAIUpvPRwM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9wbbI%2FdJMcaasD8zc%2F9KN6KNDP79TQAIUpvPRwM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;469&quot; height=&quot;163&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;163&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;Ollama&amp;nbsp;(올라마) &lt;br /&gt;로컬&amp;nbsp;머신에서&amp;nbsp;LLM을&amp;nbsp;실행시켜주는&amp;nbsp;프로그램입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Ollama 설치하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Ollama (&lt;a href=&quot;https://ollama.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com&lt;/a&gt;) 사이트에 접속합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OIru7/dJMcaiqBVzv/XcDhWoPFK09Zk9nKXMg0HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OIru7/dJMcaiqBVzv/XcDhWoPFK09Zk9nKXMg0HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OIru7/dJMcaiqBVzv/XcDhWoPFK09Zk9nKXMg0HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOIru7%2FdJMcaiqBVzv%2FXcDhWoPFK09Zk9nKXMg0HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1478&quot; height=&quot;874&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Download &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Ollama (&lt;a href=&quot;https://ollama.com/download)&quot;&gt;https://ollama.com/download)&lt;/a&gt;에서 Windows를 선택하고 다운로드를 클릭합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bszkX7/dJMcaiRIPET/0KkAi2H2ffKmRjDqnTniOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bszkX7/dJMcaiRIPET/0KkAi2H2ffKmRjDqnTniOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bszkX7/dJMcaiRIPET/0KkAi2H2ffKmRjDqnTniOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbszkX7%2FdJMcaiRIPET%2F0KkAi2H2ffKmRjDqnTniOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btGzbv/dJMcagTUy6p/krc7gVmHkZNTs3MtFIFyrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btGzbv/dJMcagTUy6p/krc7gVmHkZNTs3MtFIFyrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btGzbv/dJMcagTUy6p/krc7gVmHkZNTs3MtFIFyrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtGzbv%2FdJMcagTUy6p%2Fkrc7gVmHkZNTs3MtFIFyrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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;3. 다운로드된 OllamaSetup를 실생하여 설치합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1347&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXpaUu/dJMcajpqTrc/8Zd6rKw5PpQ0kPXX4L4Ho1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXpaUu/dJMcajpqTrc/8Zd6rKw5PpQ0kPXX4L4Ho1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXpaUu/dJMcajpqTrc/8Zd6rKw5PpQ0kPXX4L4Ho1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXpaUu%2FdJMcajpqTrc%2F8Zd6rKw5PpQ0kPXX4L4Ho1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1347&quot; height=&quot;941&quot; data-origin-width=&quot;1347&quot; data-origin-height=&quot;941&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;Ollama 설치 위치는 C:\Users\{사용자계정}\AppData\Local\Programs\Ollama 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 콘솔에서 ollama를 실행하여 사용방법을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784878568324&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zCa4f/dJMcaaMVpgi/aI8hA2hdfgw78gHpPQvsg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zCa4f/dJMcaaMVpgi/aI8hA2hdfgw78gHpPQvsg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zCa4f/dJMcaaMVpgi/aI8hA2hdfgw78gHpPQvsg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzCa4f%2FdJMcaaMVpgi%2FaI8hA2hdfgw78gHpPQvsg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;708&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Gemma&lt;span&gt;3를 사용하도록 하겠습니다. 현재는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Gemma4입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemma (&lt;a href=&quot;https://deepmind.google/models/gemma/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://deepmind.google/models/gemma/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TyE4t/dJMcagl1jOt/KBNQLPikKST8I4xVurwtA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TyE4t/dJMcagl1jOt/KBNQLPikKST8I4xVurwtA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TyE4t/dJMcagl1jOt/KBNQLPikKST8I4xVurwtA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTyE4t%2FdJMcagl1jOt%2FKBNQLPikKST8I4xVurwtA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;&lt;b&gt;Ollama에서 gemma3 다운로드 하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Ollama&amp;nbsp;Models&amp;nbsp;Search&amp;nbsp;(&lt;a href=&quot;https://ollama.com/search)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com/search&lt;/a&gt;)에서 gemma3를 검색합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkKCFk/dJMcag0AviC/Ms1hE7kKokET1voInATEr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkKCFk/dJMcag0AviC/Ms1hE7kKokET1voInATEr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkKCFk/dJMcag0AviC/Ms1hE7kKokET1voInATEr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkKCFk%2FdJMcag0AviC%2FMs1hE7kKokET1voInATEr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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;gemma3(&lt;a href=&quot;https://ollama.com/library/gemma3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com/library/gemma3&lt;/a&gt;)를 클릭하면 다양한 모델이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC에서 사용할거라 용량이 작은 모델인 gemma3:1b를 사용할 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ow7qc/dJMcadJxEDU/Nl5aC92A5r9VqXiM8p6al1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ow7qc/dJMcadJxEDU/Nl5aC92A5r9VqXiM8p6al1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ow7qc/dJMcadJxEDU/Nl5aC92A5r9VqXiM8p6al1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fow7qc%2FdJMcadJxEDU%2FNl5aC92A5r9VqXiM8p6al1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;833&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;833&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;gemma3:1b를 클릭하면 모델에 대한 정보를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VPNXa/dJMcaasD9B1/x0BjenD95T7fEZZgnqXrSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VPNXa/dJMcaasD9B1/x0BjenD95T7fEZZgnqXrSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VPNXa/dJMcaasD9B1/x0BjenD95T7fEZZgnqXrSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVPNXa%2FdJMcaasD9B1%2Fx0BjenD95T7fEZZgnqXrSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 콘솔에서 ollama pull 명령어를 사용하여 gemma3:1b를 다운로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784878728624&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama pull gemma3:1b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CKZwm/dJMcabLUDpQ/SBvM0yeo3jTdoK8Iv5khAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CKZwm/dJMcabLUDpQ/SBvM0yeo3jTdoK8Iv5khAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CKZwm/dJMcabLUDpQ/SBvM0yeo3jTdoK8Iv5khAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCKZwm%2FdJMcabLUDpQ%2FSBvM0yeo3jTdoK8Iv5khAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;708&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&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;3. 콘솔에서 ollama list 명령어를 사용하여 다운로드된 모델들을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784878892951&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GxL3Y/dJMcagTUzpa/x1IFCfH3z115biOSHZWwD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GxL3Y/dJMcagTUzpa/x1IFCfH3z115biOSHZWwD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GxL3Y/dJMcagTUzpa/x1IFCfH3z115biOSHZWwD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGxL3Y%2FdJMcagTUzpa%2Fx1IFCfH3z115biOSHZWwD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;708&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;708&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;다운로드된 모델들은 C:\Users\{사용자계정}\.ollama\models\에 저장됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzhSTs/dJMcabrwiJ2/Z4IkYyFmHlPUeNqt5CAf40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzhSTs/dJMcabrwiJ2/Z4IkYyFmHlPUeNqt5CAf40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzhSTs/dJMcabrwiJ2/Z4IkYyFmHlPUeNqt5CAf40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzhSTs%2FdJMcabrwiJ2%2FZ4IkYyFmHlPUeNqt5CAf40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;195&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;238&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;&lt;b&gt;Ollama에서 gemma3:1b 실행하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 콘솔에서 ollama run 명령어를 사용하여 gemma3:1b를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784881237603&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama run gemma3:1b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bi5gt/dJMcaiEbXTb/UzE9HBiLAtpnhW3JRtoIp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bi5gt/dJMcaiEbXTb/UzE9HBiLAtpnhW3JRtoIp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bi5gt/dJMcaiEbXTb/UzE9HBiLAtpnhW3JRtoIp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBi5gt%2FdJMcaiEbXTb%2FUzE9HBiLAtpnhW3JRtoIp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;612&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다음과 같이 질문을 해보면 답을 잘 해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784881328723&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;너는 천문학 전문가로 천문학 질문에 친절하게 답변해 주십시오. 태양계에서 가장 큰 행성은 무엇인가요?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlerY4/dJMcaheeP7f/5N6ZGc8k7Smw9iLFW58tt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlerY4/dJMcaheeP7f/5N6ZGc8k7Smw9iLFW58tt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlerY4/dJMcaheeP7f/5N6ZGc8k7Smw9iLFW58tt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlerY4%2FdJMcaheeP7f%2F5N6ZGc8k7Smw9iLFW58tt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;612&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&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;Ctrl + D를 누르면 Ollama가 종료됩니다.&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;Ollama 실행 확인하고 종료하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 콘솔에서 ollama ps 명령어를 사용하면 실행중인 모델들이 나타납니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784882045568&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama ps&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx9PB6/dJMcaf1Ht8a/7EryDcojklK1LmcfyLgNL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx9PB6/dJMcaf1Ht8a/7EryDcojklK1LmcfyLgNL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx9PB6/dJMcaf1Ht8a/7EryDcojklK1LmcfyLgNL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx9PB6%2FdJMcaf1Ht8a%2F7EryDcojklK1LmcfyLgNL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;612&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.콘솔에서 ollama stop 명령어를 사용하면 실행중인 모델을 중지됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784882206287&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ollama stop gemma3:1b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S1GQH/dJMcah6ptbl/OcgA9NCGPRmaHak9Hwj71K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S1GQH/dJMcah6ptbl/OcgA9NCGPRmaHak9Hwj71K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S1GQH/dJMcah6ptbl/OcgA9NCGPRmaHak9Hwj71K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS1GQH%2FdJMcah6ptbl%2FOcgA9NCGPRmaHak9Hwj71K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;612&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;612&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;&lt;b&gt;Ollama 앱 실행하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 윈도우 &quot;시작&quot; 버튼을 클릭하고 &quot;Ollama&quot;를 입력하고 앱을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDyKkU/dJMcajbVTp3/o8YSMhQ3LhBD7Eyne7CYB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDyKkU/dJMcajbVTp3/o8YSMhQ3LhBD7Eyne7CYB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDyKkU/dJMcajbVTp3/o8YSMhQ3LhBD7Eyne7CYB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDyKkU%2FdJMcajbVTp3%2Fo8YSMhQ3LhBD7Eyne7CYB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;814&quot; height=&quot;502&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Ollama에서 gemma3:1b를 선택하고 질문하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tz8pb/dJMcabkOrSG/zjGxO42LGBpKW6w2kMN0Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tz8pb/dJMcabkOrSG/zjGxO42LGBpKW6w2kMN0Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tz8pb/dJMcabkOrSG/zjGxO42LGBpKW6w2kMN0Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftz8pb%2FdJMcabkOrSG%2FzjGxO42LGBpKW6w2kMN0Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1036&quot; height=&quot;734&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>gemma3</category>
      <category>Ollama</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/316</guid>
      <comments>https://carrotweb.tistory.com/316#entry316comment</comments>
      <pubDate>Fri, 24 Jul 2026 17:44:13 +0900</pubDate>
    </item>
    <item>
      <title>RAG App 개발 - Open Source 모델 사용</title>
      <link>https://carrotweb.tistory.com/315</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존 소스에서 OpenAI 모델 대신 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Hugging Face (허깅 페이스)에 있는 intfloat/multilingual-e5-large-instruct&lt;/span&gt;을 사용하여 임베딩을 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Indexing&amp;nbsp;(인덱싱)&amp;nbsp;&amp;ndash;&amp;nbsp;Embed&amp;nbsp;(임베드,&amp;nbsp;임베딩&amp;nbsp;모델)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 QnA1.ipynb 파일에 텍스트 문서 임베딩 코드를 변경합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHMAd3/dJMcajbUDqT/S3Ow18g7uYEuOlQaKnC7p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHMAd3/dJMcajbUDqT/S3Ow18g7uYEuOlQaKnC7p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHMAd3/dJMcajbUDqT/S3Ow18g7uYEuOlQaKnC7p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHMAd3%2FdJMcajbUDqT%2FS3Ow18g7uYEuOlQaKnC7p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784794994771&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
일반 텍스트 문서 임베딩 (huggingface)

[Code]
%pip install langchain-huggingface

[Code]
%pip install sentence-transformers

[Code]
from langchain_huggingface import HuggingFaceEmbeddings

embeddings_model = HuggingFaceEmbeddings(model_name=&quot;intfloat/multilingual-e5-large-instruct&quot;)

#embeddings = embeddings_model.embed_documents([text.page_content for text in texts])

#len(embeddings)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;첫번째&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;[Code]를 실행하여&lt;span&gt; &lt;/span&gt;&lt;/span&gt;langchain-huggingface를 설치합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KYxfI/dJMb99NUirr/pKoz4P1RQyLyAV64HVkQik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KYxfI/dJMb99NUirr/pKoz4P1RQyLyAV64HVkQik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KYxfI/dJMb99NUirr/pKoz4P1RQyLyAV64HVkQik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKYxfI%2FdJMb99NUirr%2FpKoz4P1RQyLyAV64HVkQik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;3. 두&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;번째&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;[Code]를 실행하여&lt;/span&gt; sentence-transformers를&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beMSBC/dJMcah6odis/mXNX2PVRvKViL9SKKmcM4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beMSBC/dJMcah6odis/mXNX2PVRvKViL9SKKmcM4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beMSBC/dJMcah6odis/mXNX2PVRvKViL9SKKmcM4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeMSBC%2FdJMcah6odis%2FmXNX2PVRvKViL9SKKmcM4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 텍스트&amp;nbsp;문서&amp;nbsp;임베딩&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0CoVq/dJMcacYeSJT/sg2IvIBwJPh17tQQbSZok1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0CoVq/dJMcacYeSJT/sg2IvIBwJPh17tQQbSZok1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0CoVq/dJMcacYeSJT/sg2IvIBwJPh17tQQbSZok1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0CoVq%2FdJMcacYeSJT%2Fsg2IvIBwJPh17tQQbSZok1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;838&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈&amp;nbsp;저장&amp;nbsp;위치&amp;nbsp;:&amp;nbsp;C:\Users\{사용자계정}\.cache\huggingface\hub&lt;/p&gt;
&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;Indexing&amp;nbsp;(인덱싱)&amp;nbsp;&amp;ndash;&amp;nbsp;Store&amp;nbsp;(저장)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Chroma 대신 Faiss를 사용해보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Faiss&amp;nbsp;(&lt;a href=&quot;https://ai.meta.com/tools/faiss/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ai.meta.com/tools/faiss/)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Facebook AI Research&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 의해 개발된 라이브러리로&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대규모 벡터 데이터셋에서 유사도 검색을 빠르고 효율적으로 수행할 수 있게 해줍니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리 사용량을 최소화하면서도 검색 속도를 극대화하는 특징이 있습니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGWLYx/dJMcahrLR2P/AkfOuvmpxt6vA85yDbluOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGWLYx/dJMcahrLR2P/AkfOuvmpxt6vA85yDbluOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGWLYx/dJMcahrLR2P/AkfOuvmpxt6vA85yDbluOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGWLYx%2FdJMcahrLR2P%2FAkfOuvmpxt6vA85yDbluOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;Vector&amp;nbsp;Stores&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/how_to/vectorstores/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/how_to/vectorstores/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NzPFD/dJMcaaMUhFf/Kwa9K0v0v1NPcJ9lod2DSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NzPFD/dJMcaaMUhFf/Kwa9K0v0v1NPcJ9lod2DSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NzPFD/dJMcaaMUhFf/Kwa9K0v0v1NPcJ9lod2DSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNzPFD%2FdJMcaaMUhFf%2FKwa9K0v0v1NPcJ9lod2DSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 QnA1.ipynb 파일에 벡터 스토어 저장 코드를 변경합니다. 벡터 스토어가 저장될 faiss_db 폴더를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxWTM6/dJMcahFcHlz/rDtx4gllhz1IIhwVGBESC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxWTM6/dJMcahFcHlz/rDtx4gllhz1IIhwVGBESC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxWTM6/dJMcahFcHlz/rDtx4gllhz1IIhwVGBESC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxWTM6%2FdJMcahFcHlz%2FrDtx4gllhz1IIhwVGBESC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;pre id=&quot;code_1784795426589&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
벡터 스토어 저장 (Faiss)

[Code]
%pip install faiss-cpu

[Code]
from langchain_community.vectorstores import FAISS

vector_store = FAISS.from_documents(
    documents=texts,# 분할된 Document 객체 리스트
    embedding=embeddings_model # 사용할 임베딩 모델
)

# 저장될 디렉토리, 인덱스 명
vector_store.save_local(&quot;./faiss_db&quot;, &quot;test_db&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;첫번째&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;[Code]를 실행하여&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;faiss-cpu를&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lUG1U/dJMcafnbNds/Gb7ykGP0VKXCEzjXZaOeek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lUG1U/dJMcafnbNds/Gb7ykGP0VKXCEzjXZaOeek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lUG1U/dJMcafnbNds/Gb7ykGP0VKXCEzjXZaOeek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlUG1U%2FdJMcafnbNds%2FGb7ykGP0VKXCEzjXZaOeek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&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;3. 벡터&amp;nbsp;스토어&amp;nbsp;저장&amp;nbsp;코드를&amp;nbsp;실행합니다.&amp;nbsp;faiss_db&amp;nbsp;폴더에&amp;nbsp;벡터&amp;nbsp;스토어가&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;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAhHWC/dJMcahk1mmq/FK1niox78kS3tcoMKDIRzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAhHWC/dJMcahk1mmq/FK1niox78kS3tcoMKDIRzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAhHWC/dJMcahk1mmq/FK1niox78kS3tcoMKDIRzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAhHWC%2FdJMcahk1mmq%2FFK1niox78kS3tcoMKDIRzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 벡터 스토어 쿼리 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A2fL9/dJMcaijRlDv/Gb35NDy1tGSwd0Nw5gkakK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A2fL9/dJMcaijRlDv/Gb35NDy1tGSwd0Nw5gkakK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A2fL9/dJMcaijRlDv/Gb35NDy1tGSwd0Nw5gkakK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA2fL9%2FdJMcaijRlDv%2FGb35NDy1tGSwd0Nw5gkakK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Retrieval&amp;nbsp;(검색)&amp;nbsp;&amp;ndash;&amp;nbsp;Search&amp;nbsp;(검색)&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Faiss&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/integrations/vectorstores/faiss/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/integrations/vectorstores/faiss/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szXcW/dJMcagl0fnY/O3AYVsUPJDbBYeVV2w7Tvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szXcW/dJMcagl0fnY/O3AYVsUPJDbBYeVV2w7Tvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szXcW/dJMcagl0fnY/O3AYVsUPJDbBYeVV2w7Tvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FszXcW%2FdJMcagl0fnY%2FO3AYVsUPJDbBYeVV2w7Tvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일을&amp;nbsp;생성하고&amp;nbsp;벡터&amp;nbsp;스토어&amp;nbsp;검색&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEB43S/dJMcahedFQY/ng8hVAn6uy1I4DFDAqsUw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEB43S/dJMcahedFQY/ng8hVAn6uy1I4DFDAqsUw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEB43S/dJMcahedFQY/ng8hVAn6uy1I4DFDAqsUw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEB43S%2FdJMcahedFQY%2Fng8hVAn6uy1I4DFDAqsUw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784795892512&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
벡터 스토어 검색 (Faiss)

[Code]
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS

embeddings_model = HuggingFaceEmbeddings(model_name=&quot;intfloat/multilingual-e5-large-instruct&quot;)

loaded_vector_store = FAISS.load_local(
    folder_path=&quot;./faiss_db&quot;, # 저장된 디렉토리
    index_name=&quot;test_db&quot;, # 저장된 인덱스 명
    embeddings=embeddings_model, # 사용할 임베딩 모델
    allow_dangerous_deserialization=True # pickle 파일 역직렬화 허용 
)

retriever = loaded_vector_store.as_retriever(search_kwargs={&quot;k&quot;: 5})

result_texts = retriever.invoke(&quot;중학생의 수학 기초학력 미달률은?&quot;)
result_texts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 벡터&amp;nbsp;스토어&amp;nbsp;검색&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wMa8i/dJMcadpfwbE/LNV9hs2brLwqFNXx6IeUdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wMa8i/dJMcadpfwbE/LNV9hs2brLwqFNXx6IeUdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wMa8i/dJMcadpfwbE/LNV9hs2brLwqFNXx6IeUdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwMa8i%2FdJMcadpfwbE%2FLNV9hs2brLwqFNXx6IeUdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;벡터 스토어 검색 결과입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784795950330&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Document(id='fb8315b4-483c-4763-b361-af94b8e80ce0', metadata={'source': 'data/test.txt'}, page_content='교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학'),
 Document(id='6dcbfe08-1013-45d4-8d20-fcdbaae62393', metadata={'source': 'data/test.txt'}, page_content='8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.'),
 Document(id='241efdba-09b4-4e44-a74b-bcbd645eeb1e', metadata={'source': 'data/test.txt'}, page_content='테크빌교육 체더스, 기초학력 부진 해결을 위한 코스웨어 기획전 진행'),
 Document(id='babccbd5-7557-4c20-85ff-46534ad2b69b', metadata={'source': 'data/test.txt'}, page_content='기초 학력 향상에 특화된 에듀테크 솔루션을 특별 할인가로 제공한다.'),
 Document(id='2790ef3b-dab5-4a0e-8595-c054b6cdce53', metadata={'source': 'data/test.txt'}, page_content='이번 기획전에서는 AI 수학 학습 플랫폼 &amp;lsquo;매쓰홀릭 T&amp;rsquo;, AI 영어 학습 솔루션 &amp;lsquo;원아워&amp;rsquo;, 국어 문해력 학습 프로그램 &amp;lsquo;리드포스쿨&amp;rsquo; 등 기초 학력 향상에 특화된 에듀테크')]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #000000;&quot;&gt;Generation (&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;) &amp;ndash; LLM (&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;답변 생성&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;)&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프롬프트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B5s1Q/dJMcahSHJqg/oCZJ4HhWKcoDnzihQkwIr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B5s1Q/dJMcahSHJqg/oCZJ4HhWKcoDnzihQkwIr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B5s1Q/dJMcahSHJqg/oCZJ4HhWKcoDnzihQkwIr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB5s1Q%2FdJMcahSHJqg%2FoCZJ4HhWKcoDnzihQkwIr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;839&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;839&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;여러번&amp;nbsp;실행한&amp;nbsp;결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784796053824&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;중학생의 수학 기초학력 미달률은 13.0%입니다. 이는 다른 과목에 비해 가장 높은 수치로, 학습 격차 해소를 위한 지원이 필요하다는 점이 강조되고 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1784796074059&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;중학생의 수학 기초학력 미달률은 13.0%입니다. 이는 고등학생의 수학 기초학력 미달률인 16.6%보다 낮지만, 여전히 학습 격차 해소를 위한 지원이 필요합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>faiss</category>
      <category>faiss-cpu</category>
      <category>hugging face</category>
      <category>langchain-huggingface</category>
      <category>sentence-transformers</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/315</guid>
      <comments>https://carrotweb.tistory.com/315#entry315comment</comments>
      <pubDate>Thu, 23 Jul 2026 17:44:24 +0900</pubDate>
    </item>
    <item>
      <title>LangChain &amp;ndash; RAG + Open Source AI</title>
      <link>https://carrotweb.tistory.com/314</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Open Source Embedding Model&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유료&amp;nbsp;Embedding&amp;nbsp;Model&amp;nbsp;(임베딩&amp;nbsp;모델)&amp;nbsp;대처할&amp;nbsp;Open&amp;nbsp;Source&amp;nbsp;Embedding&amp;nbsp;Model&amp;nbsp;(임베딩&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;429&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YjaOQ/dJMcadCHVqo/GLlMikAgu7ICE3daUEjzc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YjaOQ/dJMcadCHVqo/GLlMikAgu7ICE3daUEjzc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YjaOQ/dJMcadCHVqo/GLlMikAgu7ICE3daUEjzc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYjaOQ%2FdJMcadCHVqo%2FGLlMikAgu7ICE3daUEjzc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;200&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hugging&amp;nbsp;Face&amp;nbsp;(허깅&amp;nbsp;페이스)&lt;/b&gt; &lt;br /&gt;머신 러닝 커뮤니티로 모델, 데이터 세트 그리고 애플리케이션을 협업하는 플랫폼입니다. &lt;br /&gt;기계&amp;nbsp;학습&amp;nbsp;모델을&amp;nbsp;구축,&amp;nbsp;배포&amp;nbsp;및&amp;nbsp;교육하기&amp;nbsp;위한&amp;nbsp;도구와&amp;nbsp;리소스를&amp;nbsp;개발하는&amp;nbsp;프랑스계&amp;nbsp;미국&amp;nbsp;회사이자&amp;nbsp;오픈&amp;nbsp;소스&amp;nbsp;커뮤니티이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hugging&amp;nbsp;Face&amp;nbsp;(&lt;a href=&quot;https://huggingface.co/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZaI7t/dJMcabydHLD/xJxOcVd4YWH58aclp8ENfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZaI7t/dJMcabydHLD/xJxOcVd4YWH58aclp8ENfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZaI7t/dJMcabydHLD/xJxOcVd4YWH58aclp8ENfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZaI7t%2FdJMcabydHLD%2FxJxOcVd4YWH58aclp8ENfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;LangChain은 Hugging Face 임베딩 모델을 사용하는 다양한 방법을 지원하고 있습니다. 그 중에서 Local inference(로컬 추론 방식)을 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local&amp;nbsp;inference은&amp;nbsp;모델을&amp;nbsp;다운로드하고&amp;nbsp;Sentence&amp;nbsp;Transformers를&amp;nbsp;사용하여&amp;nbsp;프로세스&amp;nbsp;내에서&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;Embed&amp;nbsp;Text&amp;nbsp;(&lt;a href=&quot;https://docs.langchain.com/oss/python/integrations/embeddings/huggingfacehub&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.langchain.com/oss/python/integrations/embeddings/huggingfacehub&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhtVqw/dJMcahLZy4E/lOPQi819IcxzkbK8dwIqfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhtVqw/dJMcahLZy4E/lOPQi819IcxzkbK8dwIqfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhtVqw/dJMcahLZy4E/lOPQi819IcxzkbK8dwIqfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhtVqw%2FdJMcahLZy4E%2FlOPQi819IcxzkbK8dwIqfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;Hugging Face (허깅 페이스)에서 무료로 사용할 모델을 검색하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Search&amp;nbsp;&amp;ndash;&amp;nbsp;sentence-transformers/all-mpnet-base-v2 &lt;br /&gt;(&lt;a href=&quot;https://huggingface.co/models?library=transformers&amp;amp;sort=trending&amp;amp;search=sentence)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/models?library=transformers&amp;amp;sort=trending&amp;amp;search=sentence)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s6efx/dJMb99NUeJb/6HaHZaTywx0XX7muXmlt51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s6efx/dJMb99NUeJb/6HaHZaTywx0XX7muXmlt51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s6efx/dJMb99NUeJb/6HaHZaTywx0XX7muXmlt51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs6efx%2FdJMb99NUeJb%2F6HaHZaTywx0XX7muXmlt51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;sentence-transformers/all-mpnet-base-v2 모델을 확인해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sentence-transformers/all-mpnet-base-v2&amp;nbsp;(&lt;a href=&quot;https://huggingface.co/sentence-transformers/all-mpnet-base-v2)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/sentence-transformers/all-mpnet-base-v2)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNFfL/dJMcaiqANQX/OIb6QfWZWu3kbfn6MnLVvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNFfL/dJMcaiqANQX/OIb6QfWZWu3kbfn6MnLVvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNFfL/dJMcaiqANQX/OIb6QfWZWu3kbfn6MnLVvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNFfL%2FdJMcaiqANQX%2FOIb6QfWZWu3kbfn6MnLVvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span&gt; LangChain에서 Hugging Face (허깅 페이스)를 사용하기 위해서 langchain-huggingface&lt;/span&gt;를 설치해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784791946854&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install -U langchain-huggingface&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Hugging Face (허깅 페이스) 모델을 사용하여 임베딩(인코딩)하여 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1N7tT/dJMcahZvp8C/sG3HY0U9TXD7bGalUc5p51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1N7tT/dJMcahZvp8C/sG3HY0U9TXD7bGalUc5p51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1N7tT/dJMcahZvp8C/sG3HY0U9TXD7bGalUc5p51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1N7tT%2FdJMcahZvp8C%2FsG3HY0U9TXD7bGalUc5p51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;184&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델의 성능을 확인할 수 있습니다. 오픈 소스라 성능은 높지 않습니다. 그렇지만 무료이기 때문에 적절하게 사용한다면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.sbert.net/docs/sentence_transformer/pretrained_models.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.sbert.net/docs/sentence_transformer/pretrained_models.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fv7ej/dJMcabydIOI/7bD0aLGhNYCELr1Pk8LQwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fv7ej/dJMcabydIOI/7bD0aLGhNYCELr1Pk8LQwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fv7ej/dJMcabydIOI/7bD0aLGhNYCELr1Pk8LQwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFv7ej%2FdJMcabydIOI%2F7bD0aLGhNYCELr1Pk8LQwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1302&quot; height=&quot;819&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;819&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;872&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bARBOO/dJMcafU0LUV/kqmA8lMK5XinEJav2Gni70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bARBOO/dJMcafU0LUV/kqmA8lMK5XinEJav2Gni70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bARBOO/dJMcafU0LUV/kqmA8lMK5XinEJav2Gni70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbARBOO%2FdJMcafU0LUV%2FkqmA8lMK5XinEJav2Gni70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;395&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;395&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;다국어 임베딩 모델 (Multilingual&amp;nbsp;Embedding&amp;nbsp;Model)&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Hugging Face (허깅 페이스)에서 많이 사용되는 &lt;/span&gt;Multilingual Embedding Model (멀티링구얼 임베딩 모델, 다국어 임베딩 모델)에 대해 알아보겠습니다.&lt;/p&gt;
&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;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델은 50개 언어를 지원합니다.&lt;/p&gt;
&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;intfloat/multilingual-e5-large-instruct&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델은 xlm-roberta에서 제공하는 100개 언어를 지원합니다. (xlm-roberta-large에서 초기화되었고 다양한 다국어 데이터셋을 사용하여 지속적으로 학습되었습니다.)&lt;/p&gt;
&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;snunlp/KR-SBERT-V40K-klueNLI-augSTS&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델은 서울대학교 컴퓨터언어학 연구실에서 개발한 모델입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KLUE-NLI 데이터 세트와 증강된 KorSTS 데이터 세트를 추가하여 SBERT 모델을 미세 조정하였습니다.&lt;/p&gt;
&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;dragonkue/snowflake-arctic-embed-l-v2.0-ko&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;모델은&amp;nbsp;Snowflake/snowflake-arctic-embed-l-v2.0&amp;nbsp;모델에&amp;nbsp;한국어&amp;nbsp;검색의&amp;nbsp;성능을&amp;nbsp;향상시키기&amp;nbsp;위해&amp;nbsp;한국어&amp;nbsp;데이터를&amp;nbsp;추가&amp;nbsp;학습하여&amp;nbsp;미세&amp;nbsp;조정하였습니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어&amp;nbsp;임베딩&amp;nbsp;모델을&amp;nbsp;비교하고&amp;nbsp;평가하는&amp;nbsp;리더보드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1429&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rCau2/dJMcafgpgFy/2eWnjCxRHnObe0R6KCKcX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rCau2/dJMcafgpgFy/2eWnjCxRHnObe0R6KCKcX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rCau2/dJMcafgpgFy/2eWnjCxRHnObe0R6KCKcX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrCau2%2FdJMcafgpgFy%2F2eWnjCxRHnObe0R6KCKcX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1429&quot; height=&quot;886&quot; data-origin-width=&quot;1429&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신의 정보는 &lt;a href=&quot;https://github.com/OnAnd0n/ko-embedding-leaderboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/OnAnd0n/ko-embedding-leaderboard&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;multilingual 모델 검색하기&lt;/b&gt;&lt;br /&gt;(&lt;a href=&quot;https://huggingface.co/models?pipeline_tag=sentence-similarity&amp;amp;library=transformers&amp;amp;sort=trending&amp;amp;search=multilingual)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/models?pipeline_tag=sentence-similarity&amp;amp;library=transformers&amp;amp;sort=trending&amp;amp;search=multilingual)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1401&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TIjLe/dJMcagfgbGi/iXRCgN9eO56GneNWydrzzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TIjLe/dJMcagfgbGi/iXRCgN9eO56GneNWydrzzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TIjLe/dJMcagfgbGi/iXRCgN9eO56GneNWydrzzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTIjLe%2FdJMcagfgbGi%2FiXRCgN9eO56GneNWydrzzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1401&quot; height=&quot;859&quot; data-origin-width=&quot;1401&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMj4hY/dJMcaixp39W/JL4QtVjXCIENyXUa8qxikK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMj4hY/dJMcaixp39W/JL4QtVjXCIENyXUa8qxikK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMj4hY/dJMcaixp39W/JL4QtVjXCIENyXUa8qxikK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMj4hY%2FdJMcaixp39W%2FJL4QtVjXCIENyXUa8qxikK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;1141&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPID7G/dJMcaiKRpts/FjeSo38etFDYMHC38ckm8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPID7G/dJMcaiKRpts/FjeSo38etFDYMHC38ckm8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPID7G/dJMcaiKRpts/FjeSo38etFDYMHC38ckm8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPID7G%2FdJMcaiKRpts%2FFjeSo38etFDYMHC38ckm8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1141&quot; height=&quot;446&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;intfloat/multilingual-e5-large-instruct&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://huggingface.co/intfloat/multilingual-e5-large-instruct)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/intfloat/multilingual-e5-large-instruct)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS0z3L/dJMcadiBeRZ/X7tDffeh2tKC1lss4axkj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS0z3L/dJMcadiBeRZ/X7tDffeh2tKC1lss4axkj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS0z3L/dJMcadiBeRZ/X7tDffeh2tKC1lss4axkj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS0z3L%2FdJMcadiBeRZ%2FX7tDffeh2tKC1lss4axkj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;모델은&amp;nbsp;xlm-roberta-large에서&amp;nbsp;초기화되었고&amp;nbsp;다양한&amp;nbsp;다국어&amp;nbsp;데이터셋을&amp;nbsp;사용하여&amp;nbsp;지속적으로&amp;nbsp;학습됩니다.&amp;nbsp;xlm-roberta에서&amp;nbsp;제공하는&amp;nbsp;100개&amp;nbsp;언어를&amp;nbsp;지원하지만,&amp;nbsp;리소스가&amp;nbsp;부족한&amp;nbsp;언어는&amp;nbsp;성능&amp;nbsp;저하가&amp;nbsp;발생할&amp;nbsp;수&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;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8vBHq/dJMcabZorwr/YjA5mDEJb1YGwi2EqBfiTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8vBHq/dJMcabZorwr/YjA5mDEJb1YGwi2EqBfiTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8vBHq/dJMcabZorwr/YjA5mDEJb1YGwi2EqBfiTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8vBHq%2FdJMcabZorwr%2FYjA5mDEJb1YGwi2EqBfiTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;XLM-RoBERTa&amp;nbsp;모델은&amp;nbsp;100개&amp;nbsp;언어가&amp;nbsp;포함된&amp;nbsp;2.5TB의&amp;nbsp;필터링된&amp;nbsp;CommonCrawl&amp;nbsp;데이터를&amp;nbsp;기반으로&amp;nbsp;사전&amp;nbsp;학습되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUov0y/dJMcaaMUeTj/kIhhscii0KVXa1gEmSmeW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUov0y/dJMcaaMUeTj/kIhhscii0KVXa1gEmSmeW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUov0y/dJMcaaMUeTj/kIhhscii0KVXa1gEmSmeW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUov0y%2FdJMcaaMUeTj%2FkIhhscii0KVXa1gEmSmeW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;744&quot; height=&quot;233&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;233&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;Embedding&amp;nbsp;Leaderboard&amp;nbsp;&amp;ndash;&amp;nbsp;Multilingual&amp;nbsp;(&lt;a href=&quot;https://huggingface.co/spaces/mteb/leaderboard)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/spaces/mteb/leaderboard)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8ovsH/dJMcah6obx9/lkk5MyO2y8S3RrjJfLFMxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8ovsH/dJMcah6obx9/lkk5MyO2y8S3RrjJfLFMxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8ovsH/dJMcah6obx9/lkk5MyO2y8S3RrjJfLFMxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8ovsH%2FdJMcah6obx9%2Flkk5MyO2y8S3RrjJfLFMxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1302&quot; height=&quot;669&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;669&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;&lt;b&gt;KR&amp;nbsp;모델&amp;nbsp;검색하기&lt;/b&gt; &lt;br /&gt;(&lt;a href=&quot;https://huggingface.co/models?pipeline_tag=sentence-similarity&amp;amp;library=transformers&amp;amp;sort=trending&amp;amp;search=KR)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/models?pipeline_tag=sentence-similarity&amp;amp;library=transformers&amp;amp;sort=trending&amp;amp;search=KR)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1401&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEoae/dJMcag0zr8A/ia6yBLSmyb7VQqYVU0A6JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEoae/dJMcag0zr8A/ia6yBLSmyb7VQqYVU0A6JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEoae/dJMcag0zr8A/ia6yBLSmyb7VQqYVU0A6JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEoae%2FdJMcag0zr8A%2Fia6yBLSmyb7VQqYVU0A6JK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1401&quot; height=&quot;859&quot; data-origin-width=&quot;1401&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;snunlp/KR-SBERT-V40K-klueNLI-augSTS&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://huggingface.co/snunlp/KR-SBERT-V40K-klueNLI-augSTS)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/snunlp/KR-SBERT-V40K-klueNLI-augSTS)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA8L4h/dJMcafU0NnT/wF4rbfAjyxEe9mta8lvRy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA8L4h/dJMcafU0NnT/wF4rbfAjyxEe9mta8lvRy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA8L4h/dJMcafU0NnT/wF4rbfAjyxEe9mta8lvRy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA8L4h%2FdJMcafU0NnT%2FwF4rbfAjyxEe9mta8lvRy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;snunlp/KR-SBERT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://github.com/snunlp/KR-SBERT)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/snunlp/KR-SBERT)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IzSHb/dJMcacjAqWf/eQM9kqyksOU0t11NcnUNNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IzSHb/dJMcacjAqWf/eQM9kqyksOU0t11NcnUNNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IzSHb/dJMcacjAqWf/eQM9kqyksOU0t11NcnUNNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIzSHb%2FdJMcacjAqWf%2FeQM9kqyksOU0t11NcnUNNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서울대학교 컴퓨터언어학 연구실에서 개발한 사전 학습된 한국어 특정 Sentence-BERT model(닐스 라이머스와 이리나 구레비치 2019)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sentence-BERT(센텐스버트,&amp;nbsp;SBERT)는&amp;nbsp;기본적으로&amp;nbsp;BERT의&amp;nbsp;문장&amp;nbsp;임베딩의&amp;nbsp;성능을&amp;nbsp;우수하게&amp;nbsp;개선시킨&amp;nbsp;모델입니다.&amp;nbsp;SBERT는&amp;nbsp;BERT의&amp;nbsp;문장&amp;nbsp;임베딩을&amp;nbsp;응용하여&amp;nbsp;BERT를&amp;nbsp;파인&amp;nbsp;튜닝하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvbEM4/dJMcacRpmuF/qeye26klGA8Ykrr8HFz4wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvbEM4/dJMcacRpmuF/qeye26klGA8Ykrr8HFz4wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvbEM4/dJMcacRpmuF/qeye26klGA8Ykrr8HFz4wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvbEM4%2FdJMcacRpmuF%2Fqeye26klGA8Ykrr8HFz4wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;745&quot; height=&quot;257&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KLUE-NLI&amp;nbsp;데이터셋과&amp;nbsp;증강&amp;nbsp;KorSTS&amp;nbsp;데이터셋을&amp;nbsp;기반으로&amp;nbsp;SBERT&amp;nbsp;모델을&amp;nbsp;파인&amp;nbsp;튜닝하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;dragonkue/snowflake-arctic-embed-l-v2.0-ko&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://huggingface.co/dragonkue/snowflake-arctic-embed-l-v2.0-ko)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/dragonkue/snowflake-arctic-embed-l-v2.0-ko)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDImSU/dJMcacRpmz2/Rdto66iPxNw4nZmHllChx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDImSU/dJMcacRpmz2/Rdto66iPxNw4nZmHllChx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDImSU/dJMcacRpmz2/Rdto66iPxNw4nZmHllChx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDImSU%2FdJMcacRpmz2%2FRdto66iPxNw4nZmHllChx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;</description>
      <category>LLM Application/LangChain</category>
      <category>hugging face</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/314</guid>
      <comments>https://carrotweb.tistory.com/314#entry314comment</comments>
      <pubDate>Thu, 23 Jul 2026 17:09:55 +0900</pubDate>
    </item>
    <item>
      <title>RAG App 개발 2 - Retrieval and Generation (검색과 생성) 하기</title>
      <link>https://carrotweb.tistory.com/313</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Retrieval&amp;nbsp;and&amp;nbsp;Generation&amp;nbsp;(검색과&amp;nbsp;생성)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;727&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OidEC/dJMcadJvP7c/QWBpu3e0C7M4OYzOYrM2N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OidEC/dJMcadJvP7c/QWBpu3e0C7M4OYzOYrM2N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OidEC/dJMcadJvP7c/QWBpu3e0C7M4OYzOYrM2N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOidEC%2FdJMcadJvP7c%2FQWBpu3e0C7M4OYzOYrM2N0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;727&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;727&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;임베딩 모델을 생성합니다.&lt;/li&gt;
&lt;li&gt;벡터 스토어에서 상위 5개만 검색되게 매개변수를 지정하여 리트리버를 인스턴스화합니다.&lt;/li&gt;
&lt;li&gt;사용자 질문을 리트리버를 사용하여 벡터 스토어에서 데이터(청크)를 검색하여 가져옵니다.&lt;/li&gt;
&lt;li&gt;검색된 데이터(청크)와 사용자 질문으로 프롬프트를 생성합니다.&lt;/li&gt;
&lt;li&gt;LLM에 사용하여 결과를 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrieval and Generation (검색과 생성) 순서대로 진행하겠습니다.&lt;/p&gt;
&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;Retrieval&amp;nbsp;(검색)&amp;nbsp;&amp;ndash;&amp;nbsp;Search&amp;nbsp;(검색)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chroma&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/integrations/vectorstores/chroma/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/integrations/vectorstores/chroma/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4J52z/dJMcahLYXGD/ffyGp1fVqFVdvtmgy9ek8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4J52z/dJMcahLYXGD/ffyGp1fVqFVdvtmgy9ek8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4J52z/dJMcahLYXGD/ffyGp1fVqFVdvtmgy9ek8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4J52z%2FdJMcahLYXGD%2FffyGp1fVqFVdvtmgy9ek8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;Retrieval&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/how_to/vectorstore_retriever/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/how_to/vectorstore_retriever/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bquIIg/dJMcaiqz971/bM1VRN2BRqh0F3hHAChDk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bquIIg/dJMcaiqz971/bM1VRN2BRqh0F3hHAChDk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bquIIg/dJMcaiqz971/bM1VRN2BRqh0F3hHAChDk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbquIIg%2FdJMcaiqz971%2FbM1VRN2BRqh0F3hHAChDk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일을&amp;nbsp;생성하고&amp;nbsp;벡터&amp;nbsp;스토어&amp;nbsp;검색&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqP7T/dJMcafna3e1/xQzEiPkKBs4C4OKBNVYBdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqP7T/dJMcafna3e1/xQzEiPkKBs4C4OKBNVYBdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqP7T/dJMcafna3e1/xQzEiPkKBs4C4OKBNVYBdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqP7T%2FdJMcafna3e1%2FxQzEiPkKBs4C4OKBNVYBdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784732862606&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
벡터 스토어 검색

[Code]
import os
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

embeddings_model = OpenAIEmbeddings(model=&quot;text-embedding-3-large&quot;)

loaded_vector_store = Chroma(
    embedding_function=embeddings_model, # 사용할 임베딩 모델
    persist_directory=&quot;./chroma_db&quot;, # 디렉토리
    collection_name=&quot;test_db&quot;
)

retriever = loaded_vector_store.as_retriever(search_kwargs={&quot;k&quot;: 5})

result_texts = retriever.invoke(&quot;중학생의 수학 기초학력 미달률은?&quot;)
result_texts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 벡터 스토어 검색 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMCPhc/dJMcab55NVh/Lv9jim8Hc5iv0Nw2wsf7iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMCPhc/dJMcab55NVh/Lv9jim8Hc5iv0Nw2wsf7iK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMCPhc/dJMcab55NVh/Lv9jim8Hc5iv0Nw2wsf7iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMCPhc%2FdJMcab55NVh%2FLv9jim8Hc5iv0Nw2wsf7iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&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;pre id=&quot;code_1784732989109&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Document(id='12c21365-cdea-4ba7-8876-eea93aa5a419', metadata={'source': 'data/test.txt'}, page_content='교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학'),
 Document(id='7cd4b316-9aed-43dd-84c5-f5f8ac59157b', metadata={'source': 'data/test.txt'}, page_content='8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.'),
 Document(id='6e29f2ff-6892-43da-8206-88c58c0dbe9b', metadata={'source': 'data/test.txt'}, page_content='테크빌교육 체더스, 기초학력 부진 해결을 위한 코스웨어 기획전 진행'),
 Document(id='40f8cf3a-7a78-4f1f-96b1-1ae2db207855', metadata={'source': 'data/test.txt'}, page_content='정지혜 체더스사업부 부서장은 &quot;학기말부터 여름방학은 기초학력을 높일 수 있는 중요한 시기&amp;rdquo;라며 &amp;ldquo;이번 기획전을 통해 선생님들이 효과가 검증된 솔루션을 활용해 학생들 간의 학습'),
 Document(id='7ab223ae-8e2a-4335-ae59-6ac3004231c7', metadata={'source': 'data/test.txt'}, page_content='이번 기획전에서는 AI 수학 학습 플랫폼 &amp;lsquo;매쓰홀릭 T&amp;rsquo;, AI 영어 학습 솔루션 &amp;lsquo;원아워&amp;rsquo;, 국어 문해력 학습 프로그램 &amp;lsquo;리드포스쿨&amp;rsquo; 등 기초 학력 향상에 특화된 에듀테크')]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Retrieval&amp;nbsp;(검색)&amp;nbsp;&amp;ndash;&amp;nbsp;Prompt&amp;nbsp;(프롬프트)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prompt&amp;nbsp;Hub&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/tutorials/rag/#orchestration)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/tutorials/rag/#orchestration)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zBRfw/dJMcadvXpbm/wa2lKQo3khhgJBeGXHFcQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zBRfw/dJMcadvXpbm/wa2lKQo3khhgJBeGXHFcQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zBRfw/dJMcadvXpbm/wa2lKQo3khhgJBeGXHFcQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzBRfw%2FdJMcadvXpbm%2Fwa2lKQo3khhgJBeGXHFcQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일에&amp;nbsp;프롬프트&amp;nbsp;가져오기&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ft7Mo/dJMcaiYlXcf/yiKpANmrVNOkkCneBXdSSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ft7Mo/dJMcaiYlXcf/yiKpANmrVNOkkCneBXdSSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ft7Mo/dJMcaiYlXcf/yiKpANmrVNOkkCneBXdSSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFt7Mo%2FdJMcaiYlXcf%2FyiKpANmrVNOkkCneBXdSSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784733084181&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
프롬프트 가져오기

[Code]
from langchain import hub

prompt = hub.pull(&quot;rlm/rag-prompt&quot;)

print(prompt)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프롬프트&amp;nbsp;가져오기&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doBSUL/dJMcaa0oFm7/R4PYovyArKKXxJmXhZFU2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doBSUL/dJMcaa0oFm7/R4PYovyArKKXxJmXhZFU2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doBSUL/dJMcaa0oFm7/R4PYovyArKKXxJmXhZFU2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoBSUL%2FdJMcaa0oFm7%2FR4PYovyArKKXxJmXhZFU2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;pre id=&quot;code_1784733145951&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;c:\workspaces\projects\project3\myenv\Lib\site-packages\langsmith\client.py:272: LangSmithMissingAPIKeyWarning: API key must be provided when using hosted LangSmith API
  warnings.warn(
input_variables=['context', 'question'] input_types={} partial_variables={} metadata={'lc_hub_owner': 'rlm', 'lc_hub_repo': 'rag-prompt', 'lc_hub_commit_hash': '50442af133e61576e74536c6556cefe1fac147cad032f4377b60c436e6cdcb6e'} messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], input_types={}, partial_variables={}, template=&quot;You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\nQuestion: {question} \nContext: {context} \nAnswer:&quot;), additional_kwargs={})]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Generation&amp;nbsp;(생성)&amp;nbsp;&amp;ndash;&amp;nbsp;LLM&amp;nbsp;(답변&amp;nbsp;생성)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA2.ipynb&amp;nbsp;파일에&amp;nbsp;프롬프트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btM20f/dJMcaae4RXQ/BkG48LGxbkQqBeWI4SNpCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btM20f/dJMcaae4RXQ/BkG48LGxbkQqBeWI4SNpCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btM20f/dJMcaae4RXQ/BkG48LGxbkQqBeWI4SNpCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtM20f%2FdJMcaae4RXQ%2FBkG48LGxbkQqBeWI4SNpCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784733201114&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
프롬프트 실행

[Code]
from langchain.chat_models import init_chat_model

model = init_chat_model(&quot;gpt-4o-mini&quot;, model_provider=&quot;openai&quot;)

texts = &quot;\n\n&quot;.join(result_text.page_content for result_text in result_texts)

do_prompt = prompt.invoke({&quot;context&quot; : texts, &quot;question&quot;: &quot;중학생의 수학 기초학력 미달률은?&quot;})

ai_msg = model.invoke(do_prompt)
print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 프롬프트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784733226689&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ChatPromptValue(messages=[HumanMessage(content='You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don\'t know the answer, just say that you don\'t know. Use three sentences maximum and keep the answer concise.\nQuestion: 중학생의 수학 기초학력 미달률은? \nContext: 교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학\n\n8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다.\n\n테크빌교육 체더스, 기초학력 부진 해결을 위한 코스웨어 기획전 진행\n\n정지혜 체더스사업부 부서장은 &quot;학기말부터 여름방학은 기초학력을 높일 수 있는 중요한 시기&amp;rdquo;라며 &amp;ldquo;이번 기획전을 통해 선생님들이 효과가 검증된 솔루션을 활용해 학생들 간의 학습\n\n이번 기획전에서는 AI 수학 학습 플랫폼 &amp;lsquo;매쓰홀릭 T&amp;rsquo;, AI 영어 학습 솔루션 &amp;lsquo;원아워&amp;rsquo;, 국어 문해력 학습 프로그램 &amp;lsquo;리드포스쿨&amp;rsquo; 등 기초 학력 향상에 특화된 에듀테크 \nAnswer:', additional_kwargs={}, response_metadata={})])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프롬프트&amp;nbsp;실행&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUt7BE/dJMcafOgRfw/ldqnkqV9z724he0d6qkGk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUt7BE/dJMcafOgRfw/ldqnkqV9z724he0d6qkGk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUt7BE/dJMcafOgRfw/ldqnkqV9z724he0d6qkGk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUt7BE%2FdJMcafOgRfw%2FldqnkqV9z724he0d6qkGk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프롬프트를 &lt;/span&gt;여러번 실행한 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784733338547&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;중학생의 수학 기초학력 미달률은 13.0%로 보고되었습니다. 이 수치는 고등학생의 수학 미달률 16.6%와 비교할 때 여전히 높은 편입니다. 따라서 학습 격차 해소를 위한 지원이 필요하다고 판단됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1784733359697&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;중학생의 수학 기초학력 미달률은 13.0%입니다. 이는 영어 6.0%와 다른 과목들에 비해 가장 높은 수치로, 학습 격차 해소를 위한 지원이 필요합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1784733370902&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;중학생의 수학 기초학력 미달률은 13.0%입니다. 이 수치는 기초학력 부진이 상대적으로 높은 것을 보여줍니다. 따라서 학습 격차 해소를 위한 지원이 필요합니다.&lt;/code&gt;&lt;/pre&gt;</description>
      <category>LLM Application/LangChain</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/313</guid>
      <comments>https://carrotweb.tistory.com/313#entry313comment</comments>
      <pubDate>Thu, 23 Jul 2026 00:22:29 +0900</pubDate>
    </item>
    <item>
      <title>RAG App 개발 1 - Indexing (인덱싱) 하기</title>
      <link>https://carrotweb.tistory.com/312</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;개발 환경 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG App를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Jupyter로 &lt;/span&gt;개발하기 위해서 VSCode에 Extension으로 Jupyter 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Jupyter&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Jupyter&lt;span&gt;는&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파이썬과&amp;nbsp;R&amp;nbsp;언어를&amp;nbsp;위해&amp;nbsp;인터렉티브한&amp;nbsp;컴퓨팅&amp;nbsp;환경을&amp;nbsp;제공하기&amp;nbsp;위해&amp;nbsp;개발되어&amp;nbsp;무료로&amp;nbsp;제공되고&amp;nbsp;있는&amp;nbsp;웹&amp;nbsp;기반의&amp;nbsp;소프트웨어이다.&amp;nbsp;Jupyter&amp;nbsp;Notebook은&amp;nbsp;셀이라는&amp;nbsp;블럭의&amp;nbsp;집합으로&amp;nbsp;코드를&amp;nbsp;작성할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;해주며,&amp;nbsp;각&amp;nbsp;셀은&amp;nbsp;따로&amp;nbsp;수정하고&amp;nbsp;실행할&amp;nbsp;수도&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6sLka/dJMcai48f4u/snEzjZuGqJdRS6wNhKvRGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6sLka/dJMcai48f4u/snEzjZuGqJdRS6wNhKvRGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6sLka/dJMcai48f4u/snEzjZuGqJdRS6wNhKvRGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6sLka%2FdJMcai48f4u%2FsnEzjZuGqJdRS6wNhKvRGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;VSCode&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jupyter Notebook &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단축키를 사용할 수 있도록 해주는 확장 프로그램입니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 확장 프로그램을 설치하면 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jupyter Notebook&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서 사용하던 단축키를 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;VS Code&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서도 동일하게 사용할 수 있습니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baoxqy/dJMcab55Jt8/CktJQvlyK3epKmIXE0Yvdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baoxqy/dJMcab55Jt8/CktJQvlyK3epKmIXE0Yvdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baoxqy/dJMcab55Jt8/CktJQvlyK3epKmIXE0Yvdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbaoxqy%2FdJMcab55Jt8%2FCktJQvlyK3epKmIXE0Yvdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;RAG App 개발&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;랭체인&amp;nbsp;사이트의&amp;nbsp;튜토리얼을&amp;nbsp;기반으로&amp;nbsp;설명하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;더&amp;nbsp;자세한&amp;nbsp;내용&amp;nbsp;랭체인&amp;nbsp;사이트를&amp;nbsp;참고하시기&amp;nbsp;바랍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(&lt;a href=&quot;https://python.langchain.com/docs/tutorials/rag/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/tutorials/rag/&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;829&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nhw2D/dJMcaaznIML/s47iiOfWVdKFUdmwYEVEf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nhw2D/dJMcaaznIML/s47iiOfWVdKFUdmwYEVEf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nhw2D/dJMcaaznIML/s47iiOfWVdKFUdmwYEVEf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnhw2D%2FdJMcaaznIML%2Fs47iiOfWVdKFUdmwYEVEf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;829&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;829&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q&amp;amp;A App 개발&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Indexing (인덱싱) 하기&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;1417&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9dssJ/dJMcahLYTt9/al6X6P4G2WYlOAKninS0ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9dssJ/dJMcahLYTt9/al6X6P4G2WYlOAKninS0ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9dssJ/dJMcahLYTt9/al6X6P4G2WYlOAKninS0ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9dssJ%2FdJMcahLYTt9%2Fal6X6P4G2WYlOAKninS0ZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1417&quot; height=&quot;713&quot; data-origin-width=&quot;1417&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;텍스트 문서 파일을 텍스트 로더로 로드합니다.&lt;/li&gt;
&lt;li&gt;텍스트 스플리터로 텍스트를 청크 사이즈로 분할합니다.&lt;/li&gt;
&lt;li&gt;임베딩 모델을 생성합니다. (청크를 벡터 값 변환)&lt;/li&gt;
&lt;li&gt;분할된 청크들을 임베딩 모델을 사용하여 벡터로 변환하고 벡터 스토어에 저장합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Indexing (인덱싱) 순서대로 진행하겠습니다.&lt;/p&gt;
&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;Indexing&amp;nbsp;(인덱싱)&amp;nbsp;&amp;ndash;&amp;nbsp;Load&amp;nbsp;(읽기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 data 폴더를 만들고 test.txt 파일을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터인 test.txt 파일은 회사 관련 뉴스입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqXKVD/dJMcabLSKkV/WtyyUEVoLQ3GKAxTc5B0QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqXKVD/dJMcabLSKkV/WtyyUEVoLQ3GKAxTc5B0QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqXKVD/dJMcabLSKkV/WtyyUEVoLQ3GKAxTc5B0QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqXKVD%2FdJMcabLSKkV%2FWtyyUEVoLQ3GKAxTc5B0QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;pre id=&quot;code_1784730520636&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;테크빌교육 체더스, 기초학력 부진 해결을 위한 코스웨어 기획전 진행

테크빌교육(대표 이형세)이 운영하는 에듀테크 마켓 플레이스 &amp;lsquo;체더스&amp;rsquo;가 여름방학을 앞두고 학생의 국&amp;middot;영&amp;middot;수 기초학력 부진 해소를 위한 &amp;lsquo;코스웨어 기획전&amp;rsquo;을 진행한다고 25일 밝혔다. 

교육부의 &amp;lsquo;2023 국가수준 학업성취도평가&amp;rsquo;에 따르면 중학생의 기초학력 미달률은 9.1%, 영어 6.0%, 수학 13.0%였으며 고등학생은 국어 8.6%, 영어 8.7%, 수학 16.6%로 집계됐다. 특히 수학의 기초학력 부진이 가장 높아 학습 격차 해소를 위한 지원이 필요한 상황이다. 

이번 기획전에서는 AI 수학 학습 플랫폼 &amp;lsquo;매쓰홀릭 T&amp;rsquo;, AI 영어 학습 솔루션 &amp;lsquo;원아워&amp;rsquo;, 국어 문해력 학습 프로그램 &amp;lsquo;리드포스쿨&amp;rsquo; 등 기초 학력 향상에 특화된 에듀테크 솔루션을 특별 할인가로 제공한다. 

체더스는 에듀테크 제품 판매에서 나아가 교육 현장에서의 활용도를 높이기 위한 다양한 지원 프로그램도 운영 중이다. 국내외 33종 에듀테크 제품의 무료 체험과 함께 초등과 중&amp;middot;고등으로 세분화된 &amp;lsquo;체더스 부트캠프&amp;rsquo;, 전국 교사 대상의 웨비나를 연 20회씩 진행하며 에듀테크 도입과 실제 수업 적용 사례를 확산하고 있다. 

최근에는 &amp;lsquo;체더스 디지털 배지&amp;rsquo; 시스템을 도입했다. 이 배지는 에듀테크 등급 배지(공교육에서 신뢰할 수 있는 품질과 가치를 제공하는 에듀테크)와 학교 실증 배지(실제 교육 현장에서 활용 돼 교육적&amp;middot;기술적 효과를 입증한 에듀테크)의 2종으로 구성돼 있다. 

올해부터 에듀테크 선도학교 예산 축소에 따라 &amp;lsquo;최저가 보장 이달의 에듀테크 특가전&amp;rsquo;도 진행하고 있다. 노션(Notion), 굿노트(GoodNotes), 카훗(Kahoot), 카미(Kami), 니어팟(Nearpod) 북크리에이터(Book Creator) 등 해외 주요 에듀테크 솔루션을 최저가로 판매하고 국내 에듀테크 기업들과의 협업으로 할인 혜택도 확대할 계획이다. 

정지혜 체더스사업부 부서장은 &quot;학기말부터 여름방학은 기초학력을 높일 수 있는 중요한 시기&amp;rdquo;라며 &amp;ldquo;이번 기획전을 통해 선생님들이 효과가 검증된 솔루션을 활용해 학생들 간의 학습 격차를 해소하는 데 도움이 되길 바란다&amp;rdquo;고 말했다. 

한편 체더스는 지난 5월 체더스에서 에듀테크 제품을 구매한 학교를 대상으로 &amp;lsquo;간식어택 이벤트&amp;rsquo;를 진행했다. 그 결과 이벤트에 선정된 8개교에 피자 및 간식 박스를 전달했으며 1등으로 당첨된 의정부신곡초등학교에는 지난 24일 &amp;lsquo;찾아가는 간식차&amp;rsquo;를 보내 디지털 교육에 앞장서고 있는 교육 현장을 직접 응원했다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. project3에 QnA1.ipynb 파일을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p3vxn/dJMcahSG3bv/462UmQT07iJ0kgFLSrkK90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p3vxn/dJMcahSG3bv/462UmQT07iJ0kgFLSrkK90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p3vxn/dJMcahSG3bv/462UmQT07iJ0kgFLSrkK90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp3vxn%2FdJMcahSG3bv%2F462UmQT07iJ0kgFLSrkK90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;텍스트 파일을 읽기(LOAD)하기 위해서 langchain-community에 있는Text Loader를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;langchain-community&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/api_reference/community/index.html)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/api_reference/community/index.html)&lt;/a&gt;&amp;nbsp;&amp;ndash;&amp;nbsp;Third-party&amp;nbsp;module&amp;nbsp;(서드파티&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;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m1Bls/dJMcabruuYj/pTIBWtVOK68cOvkxvMST8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m1Bls/dJMcabruuYj/pTIBWtVOK68cOvkxvMST8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m1Bls/dJMcabruuYj/pTIBWtVOK68cOvkxvMST8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm1Bls%2FdJMcabruuYj%2FpTIBWtVOK68cOvkxvMST8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;Document&amp;nbsp;Loader&amp;nbsp;&amp;gt;&amp;nbsp;Text&amp;nbsp;Loader&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/api_reference/community/document_loaders/langchain_&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/api_reference/community/document_loaders/langchain_&lt;/a&gt;&amp;nbsp;community.document_loaders.text.TextLoader.html)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tWcCw/dJMcaf8pMrN/3Gtik8aG0UVZZCVyK2WFNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tWcCw/dJMcaf8pMrN/3Gtik8aG0UVZZCVyK2WFNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tWcCw/dJMcaf8pMrN/3Gtik8aG0UVZZCVyK2WFNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtWcCw%2FdJMcaf8pMrN%2F3Gtik8aG0UVZZCVyK2WFNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. project3&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;QnA1.ipynb&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 파일에&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;텍스트 문서&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로드 코드를 추가합니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mFYUM/dJMcahZuM0Y/C15M3haqXAkAU4Z5TMfZsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mFYUM/dJMcahZuM0Y/C15M3haqXAkAU4Z5TMfZsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mFYUM/dJMcahZuM0Y/C15M3haqXAkAU4Z5TMfZsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmFYUM%2FdJMcahZuM0Y%2FC15M3haqXAkAU4Z5TMfZsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784730885787&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
일반 텍스트 문서 로드

[Code]
%pip install langchain-community

[Code]
from langchain_community.document_loaders import TextLoader

file_path = &quot;data/test.txt&quot;

# 텍스트 로더 생성
loader = TextLoader(file_path, encoding=&quot;utf-8&quot;)

# 문서 로드
docs = loader.load()

print(f&quot;문서의 수: {len(docs)}\n&quot;)
docs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 첫번째 [Code]를 실행하여 langchain-community를 설치합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd9SmB/dJMcacKLyLb/O2aRYHMOVoXL9naKtnNBqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd9SmB/dJMcacKLyLb/O2aRYHMOVoXL9naKtnNBqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd9SmB/dJMcacKLyLb/O2aRYHMOVoXL9naKtnNBqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd9SmB%2FdJMcacKLyLb%2FO2aRYHMOVoXL9naKtnNBqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X2QUd/dJMb99NTC1X/rXakP0I7qjETvBwgJmSpm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X2QUd/dJMb99NTC1X/rXakP0I7qjETvBwgJmSpm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X2QUd/dJMb99NTC1X/rXakP0I7qjETvBwgJmSpm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX2QUd%2FdJMb99NTC1X%2FrXakP0I7qjETvBwgJmSpm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;5. 일반 텍스트 문서 로드 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daEW2G/dJMcagGoUg1/mJmUF950tQUiDuMfWjsbB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daEW2G/dJMcagGoUg1/mJmUF950tQUiDuMfWjsbB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daEW2G/dJMcagGoUg1/mJmUF950tQUiDuMfWjsbB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaEW2G%2FdJMcagGoUg1%2FmJmUF950tQUiDuMfWjsbB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Indexing&amp;nbsp;(인덱싱)&amp;nbsp;&amp;ndash;&amp;nbsp;Split&amp;nbsp;(분할)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splitting&amp;nbsp;Document&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/tutorials/rag/#splitting-documents)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/tutorials/rag/#splitting-documents)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyVmTc/dJMcadvXoxc/knhwlkxaVmm6MGx3mJIuB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyVmTc/dJMcadvXoxc/knhwlkxaVmm6MGx3mJIuB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyVmTc/dJMcadvXoxc/knhwlkxaVmm6MGx3mJIuB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyVmTc%2FdJMcadvXoxc%2FknhwlkxaVmm6MGx3mJIuB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;1157&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6uw9G/dJMcagTSNi8/WROJXRT8v4iObkGZl8sfe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6uw9G/dJMcagTSNi8/WROJXRT8v4iObkGZl8sfe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6uw9G/dJMcagTSNi8/WROJXRT8v4iObkGZl8sfe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6uw9G%2FdJMcagTSNi8%2FWROJXRT8v4iObkGZl8sfe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1157&quot; height=&quot;182&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;182&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;Recursive&amp;nbsp;Text&amp;nbsp;Splitter&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/how_to/recursive_text_splitter/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/how_to/recursive_text_splitter/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUGf8m/dJMcadvXoA1/9Gdou079wtosn6Z5ADZvYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUGf8m/dJMcadvXoA1/9Gdou079wtosn6Z5ADZvYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUGf8m/dJMcadvXoA1/9Gdou079wtosn6Z5ADZvYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUGf8m%2FdJMcadvXoA1%2F9Gdou079wtosn6Z5ADZvYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;868&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 QnA1.ipynb 파일에 텍스트 문서 분할 코드를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8RA3D/dJMcahFbYyd/cszKtgXg1SoR2zd0KTvyp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8RA3D/dJMcahFbYyd/cszKtgXg1SoR2zd0KTvyp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8RA3D/dJMcahFbYyd/cszKtgXg1SoR2zd0KTvyp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8RA3D%2FdJMcahFbYyd%2FcszKtgXg1SoR2zd0KTvyp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784731340233&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
일반 텍스트 문서 분할

[Code]
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=20
)
texts = text_splitter.split_documents(docs)
len(texts)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 문서&amp;nbsp;분할&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sTAEy/dJMcahytNpo/94I5JhB9V5rSp3uxOb0GYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sTAEy/dJMcahytNpo/94I5JhB9V5rSp3uxOb0GYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sTAEy/dJMcahytNpo/94I5JhB9V5rSp3uxOb0GYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsTAEy%2FdJMcahytNpo%2F94I5JhB9V5rSp3uxOb0GYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Indexing&amp;nbsp;(인덱싱)&amp;nbsp;&amp;ndash;&amp;nbsp;Embed&amp;nbsp;(임베드,&amp;nbsp;임베딩&amp;nbsp;모델)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI에서&amp;nbsp;제공하는&amp;nbsp;임베딩&amp;nbsp;서비스입니다. &lt;br /&gt;텍스트를&amp;nbsp;효율적으로&amp;nbsp;벡터로&amp;nbsp;변환합니다.&amp;nbsp;하지만&amp;nbsp;비용이&amp;nbsp;발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embed&amp;nbsp;Text&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/how_to/embed_text/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/how_to/embed_text/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZpenP/dJMcaalJ5xY/QKvPKSLK48TjBcjAgTvoNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZpenP/dJMcaalJ5xY/QKvPKSLK48TjBcjAgTvoNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZpenP/dJMcaalJ5xY/QKvPKSLK48TjBcjAgTvoNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZpenP%2FdJMcaalJ5xY%2FQKvPKSLK48TjBcjAgTvoNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;Vector&amp;nbsp;embeddings&amp;nbsp;(&lt;a href=&quot;https://platform.openai.com/docs/guides/embeddings)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://platform.openai.com/docs/guides/embeddings)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3vGUm/dJMcahSG3jU/OlkSNGJYR0ji3LtHKxyvB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3vGUm/dJMcahSG3jU/OlkSNGJYR0ji3LtHKxyvB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3vGUm/dJMcahSG3jU/OlkSNGJYR0ji3LtHKxyvB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3vGUm%2FdJMcahSG3jU%2FOlkSNGJYR0ji3LtHKxyvB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&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;1299&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q8Sqw/dJMcaixpme1/2Gd12xwyq3ETUTLFVMELkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q8Sqw/dJMcaixpme1/2Gd12xwyq3ETUTLFVMELkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q8Sqw/dJMcaixpme1/2Gd12xwyq3ETUTLFVMELkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ8Sqw%2FdJMcaixpme1%2F2Gd12xwyq3ETUTLFVMELkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;834&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oRd8S/dJMcabyc3Yi/xhK7QGTBUwQltkSplSPNc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oRd8S/dJMcabyc3Yi/xhK7QGTBUwQltkSplSPNc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oRd8S/dJMcabyc3Yi/xhK7QGTBUwQltkSplSPNc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoRd8S%2FdJMcabyc3Yi%2FxhK7QGTBUwQltkSplSPNc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;295&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에&amp;nbsp;QnA1.ipynb&amp;nbsp;파일에&amp;nbsp;텍스트&amp;nbsp;문서&amp;nbsp;임베딩&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7iuWG/dJMcagTSNnh/3mvzDfTrvLnPKo43VH1ER1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7iuWG/dJMcagTSNnh/3mvzDfTrvLnPKo43VH1ER1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7iuWG/dJMcagTSNnh/3mvzDfTrvLnPKo43VH1ER1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7iuWG%2FdJMcagTSNnh%2F3mvzDfTrvLnPKo43VH1ER1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784731565327&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
일반 텍스트 문서 임베딩

[Code]
import os
from langchain_openai import OpenAIEmbeddings

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

embeddings_model = OpenAIEmbeddings(model=&quot;text-embedding-3-large&quot;)

embeddings = embeddings_model.embed_documents([text.page_content for text in texts])

len(embeddings)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 텍스트&amp;nbsp;문서&amp;nbsp;임베딩&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf1AMR/dJMcaijQBTN/phmQhW675P238Or8Jxe4XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf1AMR/dJMcaijQBTN/phmQhW675P238Or8Jxe4XK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf1AMR/dJMcaijQBTN/phmQhW675P238Or8Jxe4XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf1AMR%2FdJMcaijQBTN%2FphmQhW675P238Or8Jxe4XK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;임베딩&amp;nbsp;코드를&amp;nbsp;실행&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;1299&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4obon/dJMcaccQk86/NPfIIMlylPhcgIpbX5Tq41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4obon/dJMcaccQk86/NPfIIMlylPhcgIpbX5Tq41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4obon/dJMcaccQk86/NPfIIMlylPhcgIpbX5Tq41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4obon%2FdJMcaccQk86%2FNPfIIMlylPhcgIpbX5Tq41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;840&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Indexing&amp;nbsp;(인덱싱)&amp;nbsp;&amp;ndash;&amp;nbsp;Store&amp;nbsp;(저장)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store 중 대표적인 2개를 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chroma&amp;nbsp;(&lt;a href=&quot;https://www.trychroma.com/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.trychroma.com/)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩&amp;nbsp;벡터를&amp;nbsp;저장하기&amp;nbsp;위한&amp;nbsp;오픈소스&amp;nbsp;소프트웨어로,&amp;nbsp;LLM&amp;nbsp;App&amp;nbsp;구축을&amp;nbsp;용이하게&amp;nbsp;하는&amp;nbsp;핵심&amp;nbsp;기능을&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;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwRLgV/dJMcacDZ9oN/opuyxSjJ3cYlnfav0RVzPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwRLgV/dJMcacDZ9oN/opuyxSjJ3cYlnfav0RVzPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwRLgV/dJMcacDZ9oN/opuyxSjJ3cYlnfav0RVzPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwRLgV%2FdJMcacDZ9oN%2FopuyxSjJ3cYlnfav0RVzPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;Faiss&amp;nbsp;(&lt;a href=&quot;https://ai.meta.com/tools/faiss/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ai.meta.com/tools/faiss/)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Facebook&amp;nbsp;AI&amp;nbsp;Research에&amp;nbsp;의해&amp;nbsp;개발된&amp;nbsp;라이브러리로,&amp;nbsp;대규모&amp;nbsp;벡터&amp;nbsp;데이터셋에서&amp;nbsp;유사도&amp;nbsp;검색을&amp;nbsp;빠르고&amp;nbsp;효율적으로&amp;nbsp;수행할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해줍니다.&amp;nbsp;메모리&amp;nbsp;사용량을&amp;nbsp;최소화하면서도&amp;nbsp;검색&amp;nbsp;속도를&amp;nbsp;극대화하는&amp;nbsp;특징이&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dG8VVp/dJMcabdRxFS/AihQkd8ZhQCYq7XKZNMT21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dG8VVp/dJMcabdRxFS/AihQkd8ZhQCYq7XKZNMT21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dG8VVp/dJMcabdRxFS/AihQkd8ZhQCYq7XKZNMT21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdG8VVp%2FdJMcabdRxFS%2FAihQkd8ZhQCYq7XKZNMT21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;여기서는 Chroma를 Store로 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;langchain-chroma&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/api_reference/chroma/index.html)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/api_reference/chroma/index.html)&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z7TDb/dJMcabSv26S/DYqBtKHfCPo9O7xPpH1t4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z7TDb/dJMcabSv26S/DYqBtKHfCPo9O7xPpH1t4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z7TDb/dJMcabSv26S/DYqBtKHfCPo9O7xPpH1t4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz7TDb%2FdJMcabSv26S%2FDYqBtKHfCPo9O7xPpH1t4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&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;Vector&amp;nbsp;Stores&amp;nbsp;(&lt;a href=&quot;https://python.langchain.com/docs/how_to/vectorstores/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://python.langchain.com/docs/how_to/vectorstores/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CG51D/dJMcaixpml5/jxH74agVQYCSU468t6OcdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CG51D/dJMcaixpml5/jxH74agVQYCSU468t6OcdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CG51D/dJMcaixpml5/jxH74agVQYCSU468t6OcdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCG51D%2FdJMcaixpml5%2FjxH74agVQYCSU468t6OcdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;870&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. project3에 QnA1.ipynb 파일에 벡터 스토어 저장 코드를 추가합니다. 벡터 스토어가 저장될 chroma_db 폴더를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6cAer/dJMcaccQln1/MCL5kEM1JFCUuWjJr0hkz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6cAer/dJMcaccQln1/MCL5kEM1JFCUuWjJr0hkz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6cAer/dJMcaccQln1/MCL5kEM1JFCUuWjJr0hkz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6cAer%2FdJMcaccQln1%2FMCL5kEM1JFCUuWjJr0hkz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784732135350&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Markdown]
벡터 스토어 저장

[Code]
%pip install langchain-chroma

[Code]
from langchain_chroma import Chroma

vector_store = Chroma.from_documents(
    documents=texts, # 분할된 Document 객체 리스트
    embedding=embeddings_model, # 사용할 임베딩 모델
    persist_directory=&quot;./chroma_db&quot;, # 저장될 디렉토리
    collection_name=&quot;test_db&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 첫번째 [Code]를 실행하여 langchain-chroma를&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;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjstW9/dJMcaaMTygl/Ehvdzn9KvYlsv5rv2ag0kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjstW9/dJMcaaMTygl/Ehvdzn9KvYlsv5rv2ag0kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjstW9/dJMcaaMTygl/Ehvdzn9KvYlsv5rv2ag0kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjstW9%2FdJMcaaMTygl%2FEhvdzn9KvYlsv5rv2ag0kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;3. 벡터 스토어 저장 코드를 실행합니다. chroma_db 폴더에 벡터 스토어가 생성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q2GXj/dJMcagffvIx/b9DIUjIVdGJgvVxf4AMF20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q2GXj/dJMcagffvIx/b9DIUjIVdGJgvVxf4AMF20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q2GXj/dJMcagffvIx/b9DIUjIVdGJgvVxf4AMF20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ2GXj%2FdJMcagffvIx%2Fb9DIUjIVdGJgvVxf4AMF20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. project3에 QnA1.ipynb 파일에 벡터 스토어 쿼리 코드를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHeukm/dJMcaiEaLAi/2c9VccwqutrJmcKKEmcFB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHeukm/dJMcaiEaLAi/2c9VccwqutrJmcKKEmcFB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHeukm/dJMcaiEaLAi/2c9VccwqutrJmcKKEmcFB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHeukm%2FdJMcaiEaLAi%2F2c9VccwqutrJmcKKEmcFB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784795661169&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Code]
query = &quot;중학생의 수학 기초학력 미달률은?&quot;
docs = vector_store.similarity_search(query)
print(docs[0].page_content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 벡터&amp;nbsp;스토어&amp;nbsp;쿼리&amp;nbsp;코드를&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;1299&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsOwVl/dJMcaaGcgrO/kkWi7Qw6WqKvxAQigarVL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsOwVl/dJMcaaGcgrO/kkWi7Qw6WqKvxAQigarVL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsOwVl/dJMcaaGcgrO/kkWi7Qw6WqKvxAQigarVL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsOwVl%2FdJMcaaGcgrO%2FkkWi7Qw6WqKvxAQigarVL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;841&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;841&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;이어서, Retrieval and Generation (검색과 생성)을 진행하도록 하겠습니다.&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>jupyter</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/312</guid>
      <comments>https://carrotweb.tistory.com/312#entry312comment</comments>
      <pubDate>Thu, 23 Jul 2026 00:00:53 +0900</pubDate>
    </item>
    <item>
      <title>RAG(Retrieval-Augmented Generation)</title>
      <link>https://carrotweb.tistory.com/311</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RAG(Retrieval-Augmented Generation)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM(대규모(초거대) 언어 모델)의 출력을 최적화하여 답변을 생성하기 전에 학습 데이터 소스를 외부의 신뢰할 수 있는 지식 베이스를 참조하도록 하는 프로세스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EJqh9/dJMcajiLhuJ/DVPorcxdzVJCiDTASGOfN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EJqh9/dJMcajiLhuJ/DVPorcxdzVJCiDTASGOfN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EJqh9/dJMcajiLhuJ/DVPorcxdzVJCiDTASGOfN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEJqh9%2FdJMcajiLhuJ%2FDVPorcxdzVJCiDTASGOfN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1153&quot; height=&quot;603&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1409&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PhofD/dJMcaic9tEt/zVQh9qf7raTkZ32klZTqL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PhofD/dJMcaic9tEt/zVQh9qf7raTkZ32klZTqL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PhofD/dJMcaic9tEt/zVQh9qf7raTkZ32klZTqL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPhofD%2FdJMcaic9tEt%2FzVQh9qf7raTkZ32klZTqL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1409&quot; height=&quot;828&quot; data-origin-width=&quot;1409&quot; data-origin-height=&quot;828&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;b&gt;RAG 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 정보 : 최신 정보로 답변할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 정보 : 특정 도메인에 대한 정확한 정보로 답변할 수 있습니다. &amp;rarr; Hallucination(할루시네이션, 환각) 방지, 신뢰 강화&lt;/p&gt;
&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;RAG 단점 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변 지연 : In-Context&amp;nbsp;Learning&amp;nbsp;(ICL,&amp;nbsp;인-컨텍스트&amp;nbsp;학습)으로 인한 답변 시간이 지연될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 문제 : In-Context&amp;nbsp;Learning&amp;nbsp;(ICL,&amp;nbsp;인-컨텍스트&amp;nbsp;학습)으로 인한 민감한 정보가 외부로 노출될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RAG&amp;nbsp;프로세스&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Indexing (인덱싱)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스에서 데이터를 수집하고 인덱싱하는 파이프라인입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 오프라인에서 수행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xiokW/dJMcaaTDxWM/elijoMdwB8LOesTcJRdzAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xiokW/dJMcaaTDxWM/elijoMdwB8LOesTcJRdzAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xiokW/dJMcaaTDxWM/elijoMdwB8LOesTcJRdzAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxiokW%2FdJMcaaTDxWM%2FelijoMdwB8LOesTcJRdzAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;624&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Load (로드) :&amp;nbsp;&amp;nbsp;문서 로더를 사용하여 데이터를 로드합니다.&lt;/li&gt;
&lt;li&gt;Split (분할) : 큰 문서를 작은 청크로 나눕니다. (이는 데이터 인덱싱과 모델 전달 모두에 유용합니다. 큰 청크는 검색하기 어렵고 모델의 한정된 컨텍스트 길이에 맞지 ​​않기 때문입니다.)&lt;/li&gt;
&lt;li&gt;Embed (임베드) : 청크를 벡터 값으로 생성합니다. (Embedding Model (임베딩 모델)을 사용)&lt;/li&gt;
&lt;li&gt;Store&amp;nbsp;(저장)&amp;nbsp;:&amp;nbsp;유사도&amp;nbsp;검색을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;벡터&amp;nbsp;값(분할&amp;nbsp;데이터)를&amp;nbsp;저장합니다.&amp;nbsp;(VectorStore)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Embedding(임베딩)이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트나&amp;nbsp;이미지,&amp;nbsp;오디오&amp;nbsp;등을&amp;nbsp;Embedding&amp;nbsp;Model(임베딩&amp;nbsp;모델)을&amp;nbsp;사용하여&amp;nbsp;Vector(벡터,&amp;nbsp;숫자들로&amp;nbsp;구성된&amp;nbsp;배열)로&amp;nbsp;변환하는&amp;nbsp;과정을&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;1359&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Em85/dJMcaaMTtWL/i1KRAijTULZJwc0zAc0qj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Em85/dJMcaaMTtWL/i1KRAijTULZJwc0zAc0qj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Em85/dJMcaaMTtWL/i1KRAijTULZJwc0zAc0qj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Em85%2FdJMcaaMTtWL%2Fi1KRAijTULZJwc0zAc0qj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;621&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedding Model(임베딩 모델)은 데이터를 벡터로 변환할 수 있도록 학습된 모델을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 수학적 데이터 표현 단위를 확인해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D1Itr/dJMcahytJqE/JkFqBCPwBnTNXn8hwoI2WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D1Itr/dJMcahytJqE/JkFqBCPwBnTNXn8hwoI2WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D1Itr/dJMcahytJqE/JkFqBCPwBnTNXn8hwoI2WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD1Itr%2FdJMcahytJqE%2FJkFqBCPwBnTNXn8hwoI2WK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1359&quot; height=&quot;455&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scalar(스칼라)는 크기로만 설명되는 물리량입니다. 즉, 스칼라는 숫자 값만으로 표현되는 양입니다.&lt;/li&gt;
&lt;li&gt;Vector(벡터)는 숫자의 배열(크기와 방향을 가지고 있는 값)입니다. 숫자는 순서대로 배열되어 있으며 해당 순서의 색인을 통해 각 개별 숫자를 식별할 수 있습니다. 벡터는 공간의 점을 식별하는 것으로 생각할 수 있으며, 각 요소는 서로 다른 축을 따라 좌표를 제공합니다.&lt;/li&gt;
&lt;li&gt;Matrice(매트릭스, 행렬)는 숫자가 행과 열로 배열(스칼라 값을 2차원 형태로 배열)됩니다.&lt;/li&gt;
&lt;li&gt;Tensor(텐서)는&amp;nbsp;n-차원의&amp;nbsp;배열입니다.&amp;nbsp;n-차원&amp;nbsp;공간에서&amp;nbsp;값을&amp;nbsp;나타내며,&amp;nbsp;여러&amp;nbsp;차원으로&amp;nbsp;구성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Retrieval and Generation (검색과 생성)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp;질문을&amp;nbsp;받아&amp;nbsp;저장소에서&amp;nbsp;관련&amp;nbsp;데이터를&amp;nbsp;검색한&amp;nbsp;다음&amp;nbsp;질문과&amp;nbsp;데이터를&amp;nbsp;모델에&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;1241&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/muvqc/dJMcadJvLVi/Th43Geah72sBkQkZiZKXTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/muvqc/dJMcadJvLVi/Th43Geah72sBkQkZiZKXTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/muvqc/dJMcadJvLVi/Th43Geah72sBkQkZiZKXTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmuvqc%2FdJMcadJvLVi%2FTh43Geah72sBkQkZiZKXTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;636&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrieve (검색) :&amp;nbsp;&amp;nbsp;사용자 입력이 주어지면, 검색기를 사용하여 임베딩(벡터 값으로 생성)하고 저장소에서 데이터(청크)를 검색합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Generate (생성) : 질문과 검색된 데이터를 모두 포함하는 프롬프트를 사용하여 LLM을 통한 답변을 생성합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blYAas/dJMcabEYGUq/7jjLA34A74J8u8j6zLHsgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blYAas/dJMcabEYGUq/7jjLA34A74J8u8j6zLHsgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blYAas/dJMcabEYGUq/7jjLA34A74J8u8j6zLHsgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblYAas%2FdJMcabEYGUq%2F7jjLA34A74J8u8j6zLHsgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;795&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;795&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;</description>
      <category>LLM Application/LangChain</category>
      <category>augmented</category>
      <category>Embedding</category>
      <category>generation</category>
      <category>RAG</category>
      <category>retrieval</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/311</guid>
      <comments>https://carrotweb.tistory.com/311#entry311comment</comments>
      <pubDate>Wed, 22 Jul 2026 20:32:22 +0900</pubDate>
    </item>
    <item>
      <title>LLM 한계 및 해결방법</title>
      <link>https://carrotweb.tistory.com/310</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LMM 한계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정보의&amp;nbsp;한계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM(대규모(초거대) 언어 모델)은 선행 학습된 데이터(학습 시점의 데이터)에만 제한됩니다. 그래서 최신 정보가 부족합니다.&lt;br /&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;ChatGPT를 통해 정보의 한계를 확인해보겠습니다.&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;1245&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FF4GH/dJMcacxai4d/kng8cGGEXrVXB0yboP2Ex1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FF4GH/dJMcacxai4d/kng8cGGEXrVXB0yboP2Ex1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FF4GH/dJMcacxai4d/kng8cGGEXrVXB0yboP2Ex1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFF4GH%2FdJMcacxai4d%2Fkng8cGGEXrVXB0yboP2Ex1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;279&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ChatGPT는 학습된 정보가 없을 경우 웹에서 검색을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT가 웹에서 검색한 내용을 정리하여 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9lZXu/dJMcai47323/C8ePKGEpqeiKb7dashlaf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9lZXu/dJMcai47323/C8ePKGEpqeiKb7dashlaf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9lZXu/dJMcai47323/C8ePKGEpqeiKb7dashlaf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9lZXu%2FdJMcai47323%2FC8ePKGEpqeiKb7dashlaf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;849&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;849&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질의할때가 2025년 7월인데 ChatGPT에 데이터도 없고 웹 검색에서도 2023년를 기준으로 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 최신 데이터를 기대하고 물어보면 없거나 과거 데이터를 가져올 수 밖에 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정확성의&amp;nbsp;한계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래된 정보가 제공되거나 일반적인 정보가 제공될 수 있어 부정확한 답변이 제공될 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SsLKQ/dJMcajwiUHR/F8bvWHJaxYtk5zPshuO5NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SsLKQ/dJMcajwiUHR/F8bvWHJaxYtk5zPshuO5NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SsLKQ/dJMcajwiUHR/F8bvWHJaxYtk5zPshuO5NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSsLKQ%2FdJMcajwiUHR%2FF8bvWHJaxYtk5zPshuO5NK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;78&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;용어 혼동(동일한 용어로 인하)으로 인해 다른 내용으로 답변 생성&lt;/li&gt;
&lt;li&gt;신뢰할 수 없는 출처로부터 답변 생성&lt;/li&gt;
&lt;li&gt;사실과 다른(존재하지 않는) 정보를 사실인 것처럼 답변 생성&amp;nbsp;&amp;rarr; Hallucination(할루시네이션, 환각)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LLM&amp;nbsp;한계점&amp;nbsp;해결&amp;nbsp;방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fine-tuning&amp;nbsp;(파인&amp;nbsp;튜닝,&amp;nbsp;미세&amp;nbsp;조정)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정&amp;nbsp;도메인에&amp;nbsp;대한&amp;nbsp;높은&amp;nbsp;정확성을&amp;nbsp;제공하기&amp;nbsp;위해,&amp;nbsp;이미&amp;nbsp;훈련된&amp;nbsp;대규모&amp;nbsp;언어&amp;nbsp;모델에&amp;nbsp;특정&amp;nbsp;데이터셋을&amp;nbsp;사용하여&amp;nbsp;추가적인&amp;nbsp;학습을&amp;nbsp;수행하는&amp;nbsp;작업을&amp;nbsp;말합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H76I2/dJMcaiKQwb5/ad45PRHuVS514345NbU0m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H76I2/dJMcaiKQwb5/ad45PRHuVS514345NbU0m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H76I2/dJMcaiKQwb5/ad45PRHuVS514345NbU0m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH76I2%2FdJMcaiKQwb5%2Fad45PRHuVS514345NbU0m1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;173&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;245&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;튜닝의&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;Overtraining,&amp;nbsp;Overfitting&amp;nbsp;(과적합,&amp;nbsp;과대적합,&amp;nbsp;오버피팅)&amp;nbsp;:&amp;nbsp;학습&amp;nbsp;모델이&amp;nbsp;학습&amp;nbsp;데이터에&amp;nbsp;지나치게&amp;nbsp;잘&amp;nbsp;맞춰져&amp;nbsp;일반화하는&amp;nbsp;능력이&amp;nbsp;떨어지는&amp;nbsp;현상을&amp;nbsp;말한다.&lt;/li&gt;
&lt;li&gt;Catastrophic&amp;nbsp;Forgetting&amp;nbsp;(파멸적&amp;nbsp;망각)&amp;nbsp;:&amp;nbsp;LLM에&amp;nbsp;새로운&amp;nbsp;정보를&amp;nbsp;학습할&amp;nbsp;때&amp;nbsp;이전에&amp;nbsp;학습한&amp;nbsp;정보를&amp;nbsp;급격하게&amp;nbsp;잊어버리는&amp;nbsp;현상을&amp;nbsp;의미한다.&lt;/li&gt;
&lt;li&gt;높은 시스템 비용 : 재 학습을 위한 고성능 GPU와 대용량 메모리가 있는 시스템을 사용하기 위한 비용 부담과 재 학습 시간이 필요합니다. 추가 학습을 위한 Labeling(라벨링)과 Supervised Learning(지도학습) 과정에 많은 시간과 작업량을 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Few-Shot&amp;nbsp;Learning&amp;nbsp;(FSL,&amp;nbsp;퓨샷&amp;nbsp;학습)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0개에서 n개의 예제를 학습하여 예측을 수행하는 방법을 학습한 후 답변이 출력되도록 합니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Zero-Shot Learning (제로샷 학습) : 모델에 학습할 수 있는 예제를 제공하지 않는 방식입니다.&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;1245&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8W5CT/dJMcahLYIxq/bOcu6MVqOwkV3G3z2z8Fz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8W5CT/dJMcahLYIxq/bOcu6MVqOwkV3G3z2z8Fz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8W5CT/dJMcahLYIxq/bOcu6MVqOwkV3G3z2z8Fz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8W5CT%2FdJMcahLYIxq%2FbOcu6MVqOwkV3G3z2z8Fz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;341&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;341&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;One-Shot&amp;nbsp;Learning&amp;nbsp;(원샷&amp;nbsp;학습)&amp;nbsp;:&amp;nbsp;모델에&amp;nbsp;학습할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;하나의&amp;nbsp;예제를&amp;nbsp;제공하는&amp;nbsp;방식입니다.&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;1245&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pEOES/dJMcac4YbLg/y93QtCTibKPC0qyMksrNkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pEOES/dJMcac4YbLg/y93QtCTibKPC0qyMksrNkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pEOES/dJMcac4YbLg/y93QtCTibKPC0qyMksrNkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpEOES%2FdJMcac4YbLg%2Fy93QtCTibKPC0qyMksrNkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;868&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;868&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Few-Shot&amp;nbsp;Learning&amp;nbsp;(퓨샷&amp;nbsp;학습)&amp;nbsp;:&amp;nbsp;모델에&amp;nbsp;학습할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;여러&amp;nbsp;예제를&amp;nbsp;제공하는&amp;nbsp;방식입니다.&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;1127&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drzaOd/dJMcah6ndmu/9jXBlLE2HoWNHNjv7JTvA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drzaOd/dJMcah6ndmu/9jXBlLE2HoWNHNjv7JTvA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drzaOd/dJMcah6ndmu/9jXBlLE2HoWNHNjv7JTvA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrzaOd%2FdJMcah6ndmu%2F9jXBlLE2HoWNHNjv7JTvA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1127&quot; height=&quot;853&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Chain-of-Thought&amp;nbsp;(CoT,&amp;nbsp;생각의&amp;nbsp;사슬)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제로&amp;nbsp;문제와&amp;nbsp;함께&amp;nbsp;문제를&amp;nbsp;해결하기&amp;nbsp;위한&amp;nbsp;논리적&amp;nbsp;단계를&amp;nbsp;포함하여&amp;nbsp;프롬프트를&amp;nbsp;구성합니다.&amp;nbsp;이를&amp;nbsp;통해&amp;nbsp;모델은&amp;nbsp;유사한&amp;nbsp;추론&amp;nbsp;라인을&amp;nbsp;따르도록&amp;nbsp;유도하여&amp;nbsp;좀더&amp;nbsp;정확한&amp;nbsp;답변을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;해줍니다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l8kdU/dJMcaaMTiEV/uK0KRg37aDubfiG5nDnv40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8kdU/dJMcaaMTiEV/uK0KRg37aDubfiG5nDnv40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8kdU/dJMcaaMTiEV/uK0KRg37aDubfiG5nDnv40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8kdU%2FdJMcaaMTiEV%2FuK0KRg37aDubfiG5nDnv40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1123&quot; height=&quot;565&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;565&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;b&gt;In-Context&amp;nbsp;Learning&amp;nbsp;(ICL,&amp;nbsp;인-컨텍스트&amp;nbsp;학습)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 모델 학습과정 없이&amp;nbsp;프롬프트에 Context(문맥)를 제공하고 Context(문맥)를 기반으로 모델을 학습시켜 답변이 출력되도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QdMLU/dJMcacYdU7Z/KHk70o1k7s2ZCARyWtEvd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QdMLU/dJMcacYdU7Z/KHk70o1k7s2ZCARyWtEvd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QdMLU/dJMcacYdU7Z/KHk70o1k7s2ZCARyWtEvd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQdMLU%2FdJMcacYdU7Z%2FKHk70o1k7s2ZCARyWtEvd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;232&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;303&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지시(Instruction) &amp;ndash; 모델이 수행할 특정 작업 또는 지시 (Task Description, Persona, Role)&lt;/li&gt;
&lt;li&gt;문맥(Context) &amp;ndash; 더 나은 답변을 위해 모델을 조종할 수 있는 외부 정보나 추가 문맥 (Information, Input Indicator, Example)&lt;/li&gt;
&lt;li&gt;입력 데이터(Input Data) &amp;ndash; 답변 받고자 하는 입력이나 질문 (User Input, Question)&lt;/li&gt;
&lt;li&gt;출력&amp;nbsp;지시자(Output&amp;nbsp;Indicator)&amp;nbsp;&amp;ndash;&amp;nbsp;출력의&amp;nbsp;유형이나&amp;nbsp;형식&amp;nbsp;(Output&amp;nbsp;Indicator,&amp;nbsp;Format)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인-컨텍스트&amp;nbsp;학습의&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;Context 길이 제한 : LLM이 처리할 수 있는 컨텍스트에 길이 제한이 있습니다. 그래서 긴 컨텍스트는 처리할 수 없습니다.&lt;/li&gt;
&lt;li&gt;제공되는 Context가 너무 적거나 정보의 품질이 낮으면 모델 성능이 저하될 수 있습니다.&lt;/li&gt;
&lt;li&gt;Context가&amp;nbsp;추론이나&amp;nbsp;복잡한&amp;nbsp;논리적&amp;nbsp;사고를&amp;nbsp;요구할&amp;nbsp;경우&amp;nbsp;LLM이&amp;nbsp;정보를&amp;nbsp;처리하는데&amp;nbsp;제약이&amp;nbsp;있을&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 LangChain으로 만들 RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 LLM(대규모(초거대) 언어 모델)에 외부 데이터를 참고하여 답변하도록 In-Context Learning (ICL, 인-컨텍스트 학습)을 활용하여 만들겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 LangChain Hub에서 In-Context Learning (ICL, 인-컨텍스트 학습) 방식으로 사용될 프롬프트를 검색하여 사용해보도록 하겠습니다. 이 프롬프트는 다음에 개발할 RAG에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain&amp;nbsp;Hub&amp;nbsp;(&lt;a href=&quot;https://smith.langchain.com/hub/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://smith.langchain.com/hub/)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnjQTh/dJMcajbTItd/SJOoo7ZbghdZpWaHEEw96k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnjQTh/dJMcajbTItd/SJOoo7ZbghdZpWaHEEw96k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnjQTh/dJMcajbTItd/SJOoo7ZbghdZpWaHEEw96k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnjQTh%2FdJMcajbTItd%2FSJOoo7ZbghdZpWaHEEw96k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;865&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;865&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rag로 검색한 후 rlm/rag-prompt를 클릭합니다. 검색 후 Top Favorited를 클릭하면 상단에 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXQudn/dJMcadW8bWf/pGusuhDTo0lKPyYN7GnWA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXQudn/dJMcadW8bWf/pGusuhDTo0lKPyYN7GnWA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXQudn/dJMcadW8bWf/pGusuhDTo0lKPyYN7GnWA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXQudn%2FdJMcadW8bWf%2FpGusuhDTo0lKPyYN7GnWA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;865&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;865&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1784708062799&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Question: {question} 
Context: {context} 
Answer:&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1784707770647&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;당신은 질의응답 작업에 대한 어시스턴트입니다.
다음 문맥을 참고하여 질문에 답을 하세요.
답을 모르면 모른다고만 말씀해 주세요.
최대 세 문장으로 간결하게 답변해 주십시오.
질문: {question}
문맥: {context}
답:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한&amp;nbsp;결과를&amp;nbsp;얻기&amp;nbsp;위해서&amp;nbsp;프롬프트에&amp;nbsp;모델의&amp;nbsp;역할을&amp;nbsp;설정해주고&amp;nbsp;작업에&amp;nbsp;대한&amp;nbsp;지시사항을&amp;nbsp;설정합니다.&amp;nbsp;그리고&amp;nbsp;프롬프트에&amp;nbsp;질문과&amp;nbsp;관련된&amp;nbsp;Context(문맥)를&amp;nbsp;제공하고&amp;nbsp;Context(문맥)를&amp;nbsp;기반으로&amp;nbsp;모델을&amp;nbsp;학습시켜&amp;nbsp;답변이&amp;nbsp;출력되도록&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;1245&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MmrN2/dJMcadiAiDh/OXfNwADJKywe59u1ADcoQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MmrN2/dJMcadiAiDh/OXfNwADJKywe59u1ADcoQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MmrN2/dJMcadiAiDh/OXfNwADJKywe59u1ADcoQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMmrN2%2FdJMcadiAiDh%2FOXfNwADJKywe59u1ADcoQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1245&quot; height=&quot;713&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;713&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;In-Context Learning (ICL, 인-컨텍스트 학습)으로 할 경우 다음과 같이 고려할 사항이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프롬프트에 질문과 관련된 데이터는 어디에 있고 어떻게 가져와야 하는가?&lt;/li&gt;
&lt;li&gt;질문에 맞게 프롬프트의 역할을 어떻게 설정해야 하는가?&lt;/li&gt;
&lt;li&gt;프롬프트에서 문맥 관리는 어떻게 해야 하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 고려사항을 답을 찾아가 보도록하겠습니다.&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <category>chain-of-thought</category>
      <category>few-shot learning</category>
      <category>fine-tuning</category>
      <category>icl</category>
      <category>In-context learning</category>
      <category>LLM</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/310</guid>
      <comments>https://carrotweb.tistory.com/310#entry310comment</comments>
      <pubDate>Wed, 22 Jul 2026 17:23:09 +0900</pubDate>
    </item>
    <item>
      <title>랭체인 - LangChain</title>
      <link>https://carrotweb.tistory.com/309</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;LangChain(&lt;span style=&quot;text-align: start;&quot;&gt;랭체인&lt;/span&gt;)&lt;/b&gt; -&lt;/span&gt; &lt;a href=&quot;https://www.langchain.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.langchain.com/&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dE5eWB/dJMcaiqx82d/ZxRsRhhlJR341DQnycE261/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dE5eWB/dJMcaiqx82d/ZxRsRhhlJR341DQnycE261/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dE5eWB/dJMcaiqx82d/ZxRsRhhlJR341DQnycE261/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdE5eWB%2FdJMcaiqx82d%2FZxRsRhhlJR341DQnycE261%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1160&quot; height=&quot;850&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LangChain(&lt;span style=&quot;text-align: start;&quot;&gt;랭체인&lt;/span&gt;)은 대규모(초거대) 언어 모델(LLM)을 기반으로 애플리케이션을 개발하기 위한 프레임워크입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;2025년에 사내 개발자 교육을 위해 만들어진 내용으로 작성되었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;작성 당시 버전은 v0.3이였습니다. 현재 v1.3과 다를 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;링크는 현재 사이트에 맞게 수정하였습니다. 현재와 다르니 클릭해서 확인해보시기 바랍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;랭체인 사이트의 &lt;/span&gt;튜토리얼을 기반으로 설명하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;더 자세한 내용 랭체인 사이트를 참고하시기 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.langchain.com/oss/python/langchain/overview&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqYtFj/dJMcabdmjsB/agmDorkX4CvhBDkTlijg40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqYtFj/dJMcabdmjsB/agmDorkX4CvhBDkTlijg40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqYtFj/dJMcabdmjsB/agmDorkX4CvhBDkTlijg40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqYtFj%2FdJMcabdmjsB%2FagmDorkX4CvhBDkTlijg40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;865&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;865&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프로젝트&amp;nbsp;및&amp;nbsp;가상&amp;nbsp;환경&amp;nbsp;생성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1.&amp;nbsp;프로젝트&amp;nbsp;폴더를&amp;nbsp;생성합니다.&amp;nbsp;(저는&amp;nbsp;C:\workspaces\projects\project3&amp;nbsp;폴더를&amp;nbsp;생성하였습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2.&amp;nbsp;Visual&amp;nbsp;Studio&amp;nbsp;Code의&amp;nbsp;메뉴&amp;nbsp;File(파일)&amp;nbsp;&amp;gt;&amp;nbsp;Open&amp;nbsp;Folder...(폴더&amp;nbsp;열기)를&amp;nbsp;클릭하여&amp;nbsp;&quot;C:\workspaces\projects\project3&quot;&amp;nbsp;폴더로&amp;nbsp;이동하고&amp;nbsp;&quot;폴더&amp;nbsp;선택&quot;&amp;nbsp;버튼을&amp;nbsp;누르면&amp;nbsp;폴더가&amp;nbsp;오픈됩니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보안을&amp;nbsp;위해&amp;nbsp;Trust(신뢰)&amp;nbsp;확인&amp;nbsp;창이&amp;nbsp;나타납니다.&amp;nbsp;&quot;Yes,&amp;nbsp;I&amp;nbsp;Trust&amp;nbsp;the&amp;nbsp;authors&quot;&amp;nbsp;버튼을&amp;nbsp;누릅니다.&amp;nbsp;그리고&amp;nbsp;Welcome&amp;nbsp;창은&amp;nbsp;닫습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3.&amp;nbsp;Visual&amp;nbsp;Studio&amp;nbsp;Code의&amp;nbsp;메뉴&amp;nbsp;Terminal(터미널)&amp;nbsp;&amp;gt;&amp;nbsp;New&amp;nbsp;Terminal(새&amp;nbsp;터미널)를&amp;nbsp;클릭합니다.&amp;nbsp;그리고&amp;nbsp;가상&amp;nbsp;환경을&amp;nbsp;생성하기&amp;nbsp;위해&amp;nbsp;Terminal(터미널)에서&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;명령어를&amp;nbsp;실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780912691254&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python -m venv {가상 환경 이름 또는 경로를 포함한 가상 환경 이름}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는&amp;nbsp;가상&amp;nbsp;환경&amp;nbsp;이름을&amp;nbsp;myenv(my&amp;nbsp;environments)라고&amp;nbsp;하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780912703575&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python -m venv myenv&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4.&amp;nbsp;생성된&amp;nbsp;가상&amp;nbsp;환경을&amp;nbsp;활성화하기&amp;nbsp;위해서는&amp;nbsp;myenv의&amp;nbsp;Scripts&amp;nbsp;폴더에&amp;nbsp;있는&amp;nbsp;activate를&amp;nbsp;Terminal(터미널)에서&amp;nbsp;실행해야&amp;nbsp;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780912731355&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd myenv\Scripts
/activate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; LangChain 설치&lt;/b&gt; &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;더 자세한 내용 랭체인 사이트를 참고하시기 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;(&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.langchain.com/oss/python/langchain/install&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvATGa/dJMcagls9Rp/n7xV8EvknOOC02olKJB5v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvATGa/dJMcagls9Rp/n7xV8EvknOOC02olKJB5v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvATGa/dJMcagls9Rp/n7xV8EvknOOC02olKJB5v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvATGa%2FdJMcagls9Rp%2Fn7xV8EvknOOC02olKJB5v1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;845&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재&amp;nbsp;가상&amp;nbsp;환경에&amp;nbsp;LangChain(랭체인)을&amp;nbsp;설치하기&amp;nbsp;위해&amp;nbsp;Terminal(터미널)에서&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;명령어를&amp;nbsp;실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780912168690&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install langchain&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lhM9R/dJMcajicgEe/1GqDiLoQa3OTOoWuqEJPrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lhM9R/dJMcajicgEe/1GqDiLoQa3OTOoWuqEJPrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lhM9R/dJMcajicgEe/1GqDiLoQa3OTOoWuqEJPrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlhM9R%2FdJMcajicgEe%2F1GqDiLoQa3OTOoWuqEJPrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LangChain(랭체인)이 설치됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/462EQ/dJMcacDqYMc/EbDASMe8khUw31KjG8M26k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/462EQ/dJMcacDqYMc/EbDASMe8khUw31KjG8M26k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/462EQ/dJMcacDqYMc/EbDASMe8khUw31KjG8M26k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F462EQ%2FdJMcacDqYMc%2FEbDASMe8khUw31KjG8M26k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 환경 폴더(myenv)의 Lib\site-packages 폴더를 보면 LangChain(랭체인) 관련 모듈들이 설치되어 있는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OI8Ek/dJMcaaMkA3E/OR0B15KIkfMfqzFpbKJGm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OI8Ek/dJMcaaMkA3E/OR0B15KIkfMfqzFpbKJGm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OI8Ek/dJMcaaMkA3E/OR0B15KIkfMfqzFpbKJGm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOI8Ek%2FdJMcaaMkA3E%2FOR0B15KIkfMfqzFpbKJGm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;LangChain(랭체인)에서 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;openAI 모델을 사용하기 위해서&lt;span&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;openAI 모델의 &lt;/span&gt;&lt;/span&gt;통합 패키지인 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;langchain-openai&lt;/span&gt;를 설치해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;langchain-openai 설치&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;더 자세한 내용 랭체인 사이트를 참고하시기 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;(&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.langchain.com/oss/python/langchain/install&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chxxc2/dJMcaccjIe0/Zy3ycFzYE4d4QgMM6lKWkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chxxc2/dJMcaccjIe0/Zy3ycFzYE4d4QgMM6lKWkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chxxc2/dJMcaccjIe0/Zy3ycFzYE4d4QgMM6lKWkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchxxc2%2FdJMcaccjIe0%2FZy3ycFzYE4d4QgMM6lKWkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;868&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;현재 가상 환경에 langchain-openai를 설치하기 위해 Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780913139107&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install langchain-openai&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UofIT/dJMcagy0rKW/dOZvLKc96mmFa3KyAiVOAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UofIT/dJMcagy0rKW/dOZvLKc96mmFa3KyAiVOAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UofIT/dJMcagy0rKW/dOZvLKc96mmFa3KyAiVOAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUofIT%2FdJMcagy0rKW%2FdOZvLKc96mmFa3KyAiVOAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;langchain-openai가 설치됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crnStr/dJMcaiKkbKp/vsSKuQkp1ruooefNQMXJYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crnStr/dJMcaiKkbKp/vsSKuQkp1ruooefNQMXJYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crnStr/dJMcaiKkbKp/vsSKuQkp1ruooefNQMXJYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrnStr%2FdJMcaiKkbKp%2FvsSKuQkp1ruooefNQMXJYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 환경 폴더(myenv)의 Lib\site-packages 폴더를 보면 langchain-openai 관련 모듈들이 설치되어 있는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bddzaJ/dJMcagsfCLB/QNykKC7d58EhirvZJASJ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bddzaJ/dJMcagsfCLB/QNykKC7d58EhirvZJASJ61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bddzaJ/dJMcagsfCLB/QNykKC7d58EhirvZJASJ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbddzaJ%2FdJMcagsfCLB%2FQNykKC7d58EhirvZJASJ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;간단한 채팅 만들기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;더 자세한 내용 랭체인 사이트를 참고하시기 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;(&lt;a href=&quot;https://docs.langchain.com/oss/python/langchain/quickstart&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.langchain.com/oss/python/langchain/quickstart&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;현재 v1.3에는 Agent로 되어있습니다. v0.3이라 설명이 다릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGfUlM/dJMcaiwQfOs/HHTK2JIu5pQvBkaYZmCnXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGfUlM/dJMcaiwQfOs/HHTK2JIu5pQvBkaYZmCnXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGfUlM/dJMcaiwQfOs/HHTK2JIu5pQvBkaYZmCnXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGfUlM%2FdJMcaiwQfOs%2FHHTK2JIu5pQvBkaYZmCnXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;866&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&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;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;init_chat_model&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; (통합 모델 초기화)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;통합&amp;nbsp;인터페이스를&amp;nbsp;사용하여&amp;nbsp;지원되는&amp;nbsp;모든&amp;nbsp;제공업체의&amp;nbsp;채팅&amp;nbsp;모델을&amp;nbsp;초기화합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(&lt;a href=&quot;https://reference.langchain.com/python/langchain/chat_models/base/init_chat_model&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://reference.langchain.com/python/langchain/chat_models/base/init_chat_model&lt;/a&gt;)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781065224047&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;init_chat_model(
  model: str | None = None,
  model_provider: str | None = None,
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;model : 사용할 모델 명 (&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;openAI&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 모델을 사용할 것이기 때문에 gpt-로 시작하는 모델명을 입력합니다. 비용을 고려해서 gpt-4o-mini로 사용했습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;model_provider : 모델을 제공하는 업체 명 (&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;openAI&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;모델을 사용할 것이기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;openai를 입력합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;코드 내에서 API Key를 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;현경 변수로 &lt;/span&gt;직접 적용하기 위해서 os.environ를 사용합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784531484878&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;{API Key}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수명은 &quot;OPENAI_API_KEY&quot;로 값은 openAI에서 생성한 Key를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 프롬프트를 채팅 모델로 호출하여 응답을 AIMessage 객체로 받아옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784531744532&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ai_msg = model.invoke({message})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message : 메시지 프롬프트를 입력합니다. 간단하게 텍스트로 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 결과을 출력하기 위해 AIMessage 객체에서 content로 결과를 가져옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784532355749&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;project3에&amp;nbsp;main.py&amp;nbsp;파일을&amp;nbsp;생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780913936045&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
from langchain.chat_models import init_chat_model

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;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&quot;

model = init_chat_model(&quot;gpt-4o-mini&quot;, model_provider=&quot;openai&quot;)

ai_msg = model.invoke(&quot;Hello, world!&quot;)
print(ai_msg.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b88mU8/dJMcaaS7uxo/nkYpab1IVBTwogeiS5SKMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b88mU8/dJMcaaS7uxo/nkYpab1IVBTwogeiS5SKMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b88mU8/dJMcaaS7uxo/nkYpab1IVBTwogeiS5SKMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb88mU8%2FdJMcaaS7uxo%2FnkYpab1IVBTwogeiS5SKMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;실행하기 위해&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780914029760&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python main.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tte7N/dJMb99NnVMP/LhUFlsKp20OaLZIvckE3pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tte7N/dJMb99NnVMP/LhUFlsKp20OaLZIvckE3pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tte7N/dJMb99NnVMP/LhUFlsKp20OaLZIvckE3pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTte7N%2FdJMb99NnVMP%2FLhUFlsKp20OaLZIvckE3pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;842&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;openAI의 gpt-4o-mini 모델을 이용한 응답 결과가 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp2kQS/dJMcadIYwEW/bYqX52ionDmKsqH0l3Pam0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp2kQS/dJMcadIYwEW/bYqX52ionDmKsqH0l3Pam0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp2kQS/dJMcadIYwEW/bYqX52ionDmKsqH0l3Pam0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp2kQS%2FdJMcadIYwEW%2FbYqX52ionDmKsqH0l3Pam0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&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;pre id=&quot;code_1780914247240&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;입력 메시지 : Hello, world! (4 token)
응답 메시지 : Hello! How can I assist you today? (9 token)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;content='Hello!&amp;nbsp;How&amp;nbsp;can&amp;nbsp;I&amp;nbsp;assist&amp;nbsp;you&amp;nbsp;today?'&amp;nbsp;additional_kwargs={'refusal':&amp;nbsp;None}&amp;nbsp;response_metadata={'token_usage':&amp;nbsp;{'completion_tokens':&amp;nbsp;9,&amp;nbsp;'prompt_tokens':&amp;nbsp;27,&amp;nbsp;'total_tokens':&amp;nbsp;36,&amp;nbsp;'completion_tokens_details':&amp;nbsp;{'accepted_prediction_tokens':&amp;nbsp;0,&amp;nbsp;'audio_tokens':&amp;nbsp;0,&amp;nbsp;'reasoning_tokens':&amp;nbsp;0,&amp;nbsp;'rejected_prediction_tokens':&amp;nbsp;0},&amp;nbsp;'prompt_tokens_details':&amp;nbsp;{'audio_tokens':&amp;nbsp;0,&amp;nbsp;'cached_tokens':&amp;nbsp;0}},&amp;nbsp;'model_name':&amp;nbsp;'gpt-4o-2024-08-06',&amp;nbsp;'system_fingerprint':&amp;nbsp;'fp_a288987b44',&amp;nbsp;'id':&amp;nbsp;'chatcmpl-BqFjFY82nFOkuR2XNQte8UD9zGs6A',&amp;nbsp;'service_tier':&amp;nbsp;'default',&amp;nbsp;'finish_reason':&amp;nbsp;'stop',&amp;nbsp;'logprobs':&amp;nbsp;None}&amp;nbsp;id='run--e8c00ef2-1f2c-4c5c-b203-8570c3b0c5e0-0'&amp;nbsp;usage_metadata={'input_tokens':&amp;nbsp;27,&amp;nbsp;'output_tokens':&amp;nbsp;9,&amp;nbsp;'total_tokens':&amp;nbsp;36,&amp;nbsp;'input_token_details':&amp;nbsp;{'audio':&amp;nbsp;0,&amp;nbsp;'cache_read':&amp;nbsp;0},&amp;nbsp;'output_token_details':&amp;nbsp;{'audio':&amp;nbsp;0,&amp;nbsp;'reasoning':&amp;nbsp;0}}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;OpanAI&amp;nbsp;사용내역&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OpenAI Platform에서 사용량을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://platform.openai.com/usage&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://platform.openai.com/usage&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxCdSN/dJMb99UbOYL/LNhwRhMBxwvOJod3fiDLM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxCdSN/dJMb99UbOYL/LNhwRhMBxwvOJod3fiDLM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxCdSN/dJMb99UbOYL/LNhwRhMBxwvOJod3fiDLM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxCdSN%2FdJMb99UbOYL%2FLNhwRhMBxwvOJod3fiDLM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;866&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;866&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;추가로&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;할당량이 초과될 경우 다음과 같은 에러 메시지가 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1LzK9/dJMcagTld2H/94WJdWqyAACzhPhaPq8Dc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1LzK9/dJMcagTld2H/94WJdWqyAACzhPhaPq8Dc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1LzK9/dJMcagTld2H/94WJdWqyAACzhPhaPq8Dc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1LzK9%2FdJMcagTld2H%2F94WJdWqyAACzhPhaPq8Dc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1299&quot; height=&quot;843&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1780914568861&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;에러 메시지 : You exceeded your current quota, please check your plan and billing details.
현재 할당량을 초과했습니다. 요금제 및 청구 세부 정보를 확인하세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;잘못된 API 키를 사용할 경우에는 다음과 같은 에러 메시지가 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY276j/dJMcabYOWFT/JXdLbVe8RKb1zpPc5rMoB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY276j/dJMcabYOWFT/JXdLbVe8RKb1zpPc5rMoB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY276j/dJMcabYOWFT/JXdLbVe8RKb1zpPc5rMoB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY276j%2FdJMcabYOWFT%2FJXdLbVe8RKb1zpPc5rMoB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;881&quot; height=&quot;256&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1780914597219&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;에러 메시지 : Incorrect API key provided
잘못된 API 키가 제공되었습니다.

API 키는 https://platform.openai.com/account/api-keys에서 찾을 수 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>LLM Application/LangChain</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/309</guid>
      <comments>https://carrotweb.tistory.com/309#entry309comment</comments>
      <pubDate>Mon, 8 Jun 2026 19:46:38 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 장고 사이트 국제화, 시간 설정 - LANGUAGE_CODE, TIME_ZONE, USE_I18N, USE_TZ</title>
      <link>https://carrotweb.tistory.com/308</link>
      <description>&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;2604&quot; data-origin-height=&quot;1518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btajks/btsKujOx8B4/zcIxCndMVYGiYJ5zju5pXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btajks/btsKujOx8B4/zcIxCndMVYGiYJ5zju5pXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btajks/btsKujOx8B4/zcIxCndMVYGiYJ5zju5pXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtajks%2FbtsKujOx8B4%2FzcIxCndMVYGiYJ5zju5pXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2604&quot; height=&quot;1518&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1518&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;사이트에 표시되는 언어를 영어에서 한국어로 변경하고 싶을 경우에는 LANGUAGE_CODE(언어 코드)를 변경하면 됩니다.&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; Internationalization(국제화) 설정&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;config 폴더에 있는 settings.py 파일에서 internationalization(국제화) 영역에 있는 LANGUAGE_CODE를 'en-us'에서 'ko-kr'로 변경하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730642894527&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 언어 - 한국어
LANGUAGE_CODE = 'ko-kr'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diL9U7/btsKtNWaAP8/anT8Krun4A65UFCW305uv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diL9U7/btsKtNWaAP8/anT8Krun4A65UFCW305uv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diL9U7/btsKtNWaAP8/anT8Krun4A65UFCW305uv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiL9U7%2FbtsKtNWaAP8%2FanT8Krun4A65UFCW305uv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 사이트가 한국어로 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRdTy7/btsKvIsMt3Z/oiyMj5c28eEBYHSSwKoaIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRdTy7/btsKvIsMt3Z/oiyMj5c28eEBYHSSwKoaIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRdTy7/btsKvIsMt3Z/oiyMj5c28eEBYHSSwKoaIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRdTy7%2FbtsKvIsMt3Z%2FoiyMj5c28eEBYHSSwKoaIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2604&quot; height=&quot;1518&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1518&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;장고가 제공하는 메인 페이지는 myenv(가상환경)&amp;nbsp;&amp;gt;&amp;nbsp;Lib&amp;nbsp;&amp;gt;&amp;nbsp;site-packages&amp;nbsp;&amp;gt;&amp;nbsp;django&amp;nbsp;&amp;gt; views &amp;gt; templates 폴더에 있는 default_urlconf.html 파일입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rBg8A/btsKuxFPVhq/FFi5YcAgNIq1HUeMjHKObK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rBg8A/btsKuxFPVhq/FFi5YcAgNIq1HUeMjHKObK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rBg8A/btsKuxFPVhq/FFi5YcAgNIq1HUeMjHKObK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrBg8A%2FbtsKuxFPVhq%2FFFi5YcAgNIq1HUeMjHKObK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&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;메인 페이지(default_urlconf.html)를 보면 상단에 load 템플릿 태그가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730645282093&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{% load i18n %}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 internationalization(국제화)를 로드하여 템플릿 페이지에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;번역된 문장열을 사용하겠다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서, i18(또는 대문자로 I18N)이 무슨 단어이기에 국제화라는 걸까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i18n은 internationalization의 첫 번째 단어 i부터 끝단어 n사이에 18개의 문자가 있는 것을 합처서&amp;nbsp;i18n으로 약어를 만든 겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730645808635&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;i nternationalizatio n
  ------------------
                  18

i18n&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 localization(지역화 또는 현지화)는 무슨 단어일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네, 예상하신 대로 l10n(또는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;대문자로&lt;span&gt; L10N)입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730646066729&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;l ocalizatio n
  ----------
          10

l10n&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;템플릿 페이지에서 internationalization(국제화)로 번역된 문자열을 사용하고 싶을 때는 translate 템플릿 태그를 사용하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 페이지(default_urlconf.html)처럼 번역된 문자열을 사용하고 싶은 문자열마다 translate 템플릿 태그를 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730646224816&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;title&amp;gt;{% translate &quot;The install worked successfully! Congratulations!&quot; %}&amp;lt;/title&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 번역된 문자열들은 어디에 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역된 문자열들은 myenv(가상환경) &amp;gt; Lib &amp;gt; site-packages &amp;gt; django &amp;gt; conf &amp;gt; locale 폴더에 언어 코드별로 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한국어로 번역된 문자열들은 myenv(가상환경) &amp;gt; Lib &amp;gt; site-packages &amp;gt; django &amp;gt; conf &amp;gt; locale &amp;gt; ko &amp;gt; LC_MESSAGES 폴더에 있는 django.po&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oqd2u/btsKvhh9Hmk/PNkO9i3vLxy2yU7lKay8Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oqd2u/btsKvhh9Hmk/PNkO9i3vLxy2yU7lKay8Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oqd2u/btsKvhh9Hmk/PNkO9i3vLxy2yU7lKay8Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foqd2u%2FbtsKvhh9Hmk%2FPNkO9i3vLxy2yU7lKay8Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&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;원문과 번역문은 msgid와 msgstr로 구성되어 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730647448467&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;msgid &quot;The install worked successfully! Congratulations!&quot;
msgstr &quot;성공적으로 설치되었습니다! 축하합니다!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;msgid는 translate 템플릿 태그에 있는 문자열입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730647746052&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{% translate &quot;The install worked successfully! Congratulations!&quot; %}
              -------------------------------------------------
                                                          msgid&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;msgstr은 번역된 문자열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 국제화를 사용하고 싶지 않을 경우에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;config 폴더에 있는 settings.py 파일에서 internationalization(국제화) 영역에 있는&amp;nbsp;&lt;/span&gt;USE_I18N를 False로 하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730648528596&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 국제화를 사용하지 않음
USE_I18N = False&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQGS8q/btsKuPTPhbe/GhWONWDKomPUEDQ0idAFwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQGS8q/btsKuPTPhbe/GhWONWDKomPUEDQ0idAFwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQGS8q/btsKuPTPhbe/GhWONWDKomPUEDQ0idAFwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQGS8q%2FbtsKuPTPhbe%2FGhWONWDKomPUEDQ0idAFwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;템플릿 페이지에 있는&lt;span&gt; 문장이 그대로 나옵니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;우리는 관리자 페이지를 한국어로 표시할 것이기 때문에 &lt;/span&gt;&lt;/span&gt;USE_I18N은 True로 변경하지 않을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국제화와 번역에 대한 더 자세한 내용 장고 문서 사이트를 참고하시기 바랍니다. (&lt;a href=&quot;https://docs.djangoproject.com/en/5.1/topics/i18n/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.djangoproject.com/en/5.1/topics/i18n/&lt;/a&gt;, &lt;a href=&quot;https://docs.djangoproject.com/en/5.1/topics/i18n/translation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.djangoproject.com/en/5.1/topics/i18n/translation/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 페이지에서 사용할 국제화 생성에 대해서 다음에 앱을 만들고 템플릿 페이지를 만들 때 다시 설명하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&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;장고는 기본적으로 TIME_ZONE은 UTC(Universal Time Coordinated, &lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;협정 세계시&lt;/span&gt;)으로 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config 폴더에 있는 settings.py 파일에서 internationalization(국제화) 영역에 있는 TIME_ZONE이 UTC으로 설정되어 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730650129577&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TIME_ZONE = 'UTC'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCTdYc/btsKucoCdcs/lZhvloCbzpXlfcvMovZpkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCTdYc/btsKucoCdcs/lZhvloCbzpXlfcvMovZpkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCTdYc/btsKucoCdcs/lZhvloCbzpXlfcvMovZpkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCTdYc%2FbtsKucoCdcs%2FlZhvloCbzpXlfcvMovZpkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 한국 시간으로 표시하려면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TIME_ZONE를 Asia/Seoul로 변경하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730651228633&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 시간 - 한국
TIME_ZONE = 'Asia/Seoul'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m3FZ1/btsKtWM9cQp/fyZcYddMinmJvGdyQbRk9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m3FZ1/btsKtWM9cQp/fyZcYddMinmJvGdyQbRk9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m3FZ1/btsKtWM9cQp/fyZcYddMinmJvGdyQbRk9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm3FZ1%2FbtsKtWM9cQp%2FfyZcYddMinmJvGdyQbRk9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 한국 시간으로 시간이 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;여기서 주의할 점!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 저장될 때는 UTC로 저장되는 겁니다.&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;다음처럼, 현재 한국 시간이 2024년 11월 3일 오후 7시 39분 34초라면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터베이스에 저장될 때는&lt;span&gt; UTC로 변형되어 저장됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730653060384&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UTC로 저장되는 시간
datetime.datetime(2024, 11, 3, 10, 39, 34, tzinfo=datetime.timezone.utc)

한국 시간으로 처리된 시간
datetime.datetime(2024, 11, 3, 19, 39, 34, tzinfo=&amp;lt;DstTzInfo 'Asia/Seoul' LMT+9:00:00 STD&amp;gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 데이터베이스에 저장된 시간은 한국 시간 기준보다 9시간이 느리게 저장이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730653741049&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UTC
Asia/Seoul = UTC + 9&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 시스템(서버) 시간이 한국 시간 기준일 경우 데이터베이스에 저장된 시간과 맞지 않아서 서버 프로그램에 개발에 문제가 될 수 도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터베이스에&lt;span&gt; 저장되는 시간을 시스템 시간(한국 시간 기준)으로 하려면 어떻게 해야 할까요?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;TIME_ZONE를 사용하지 않으면 됩니다. 그래서 USE_TZ를&amp;nbsp;&lt;/span&gt;False로 변경하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730655184972&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# TIME_ZONE를 사용하지 않음
USE_TZ = False&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmV8em/btsKukmtHAb/LiFCILHw2hlkerGVs8veaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmV8em/btsKukmtHAb/LiFCILHw2hlkerGVs8veaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmV8em/btsKukmtHAb/LiFCILHw2hlkerGVs8veaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmV8em%2FbtsKukmtHAb%2FLiFCILHw2hlkerGVs8veaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; TIME_ZONE를 사용하지 않기 때문에 데이터베이스에 저장된 시간은 시스템 시간(한국 시간 기준)이 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730656114992&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;USE_TZ = True --&amp;gt; UTC 시간으로 저장됩니다.
USE_TZ = False --&amp;gt; 시스템 시간으로 저장됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;만약, 국제화를 사용하지 않고 시스템 시간(한국 시간 기준)을 기준으로 하고 싶을 경우에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;USE_I18N과 USE_TZ를 False로 설정하면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730655856734&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 국제화를 사용하지 않음
USE_I18N = False

# TIME_ZONE를 사용하지 않음
USE_TZ = 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;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;우리는 시스템 시간(한국 시간 기준)으로 저장할 것이 때문에 USE_TZ를 False로 설정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>django</category>
      <category>default_urlconf.html</category>
      <category>django</category>
      <category>i18n</category>
      <category>internationalization</category>
      <category>LANGUAGE_CODE</category>
      <category>TIME_ZONE</category>
      <category>use_i18n</category>
      <category>USE_TZ</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/308</guid>
      <comments>https://carrotweb.tistory.com/308#entry308comment</comments>
      <pubDate>Mon, 4 Nov 2024 02:51:36 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 장고 마이그레이션 - makemigrations, migrate, reversing migrations</title>
      <link>https://carrotweb.tistory.com/307</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;장고 기반 프로젝트를 처음 실행하면 터미널(Terminal)에 다음과 같이 붉은색으로 마이그레이션(Migration)에 대한 경고를 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvvGZ/btsKdGhVp99/npE0vnTeiVRjpfPcjCl5Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvvGZ/btsKdGhVp99/npE0vnTeiVRjpfPcjCl5Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvvGZ/btsKdGhVp99/npE0vnTeiVRjpfPcjCl5Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvvGZ%2FbtsKdGhVp99%2FnpE0vnTeiVRjpfPcjCl5Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1716&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1716&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;You&amp;nbsp;have&amp;nbsp;18&amp;nbsp;unapplied&amp;nbsp;migration(s).&amp;nbsp;Your&amp;nbsp;project&amp;nbsp;may&amp;nbsp;not&amp;nbsp;work&amp;nbsp;properly&amp;nbsp;until&amp;nbsp;you&amp;nbsp;apply&amp;nbsp;the&amp;nbsp;migrations&amp;nbsp;for&amp;nbsp;app(s):&amp;nbsp;admin,&amp;nbsp;auth,&amp;nbsp;contenttypes,&amp;nbsp;sessions. &lt;br /&gt;Run&amp;nbsp;'python&amp;nbsp;manage.py&amp;nbsp;migrate'&amp;nbsp;to&amp;nbsp;apply&amp;nbsp;them.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역하면 적용되지 않은 마이그레이션이 18개 있습니다. 당신의 프로젝트가 앱(admin, auth, contenttypes, sessions)에 대한 마이그레이션을 할 때까지 제대로 작동하지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용하려면 'python manage.py migrate'을 실행하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경고가 발생한 이유는 장고 기반 프로젝트는 기본적으로 관리자 기능이 함께 제공되기 때문입니다. 그래서 관리자 앱(App)에 있는 모델(Model)들과 연동되는 테이블(Table)이 데이터베이스(Database)에 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고 관리자 앱(admin, auth, contenttypes, sessions)은 어디에 있나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config 폴더에 있는 settings.py 파일에서 INSTALLED_APPS에 설정되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KSmT3/btsKljHVJPR/gWoQmLnOmgWjPZkfbkA4sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KSmT3/btsKljHVJPR/gWoQmLnOmgWjPZkfbkA4sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KSmT3/btsKljHVJPR/gWoQmLnOmgWjPZkfbkA4sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKSmT3%2FbtsKljHVJPR%2FgWoQmLnOmgWjPZkfbkA4sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;관리자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 앱들은 myenv(가상환경) &amp;gt; Lib &amp;gt; site-packages &amp;gt; django &amp;gt; contrib 폴더에 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6sSMG/btsKvFJz5ey/OqwQYlIHZvVsY6j1WKfWB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6sSMG/btsKvFJz5ey/OqwQYlIHZvVsY6j1WKfWB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6sSMG/btsKvFJz5ey/OqwQYlIHZvVsY6j1WKfWB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6sSMG%2FbtsKvFJz5ey%2FOqwQYlIHZvVsY6j1WKfWB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1728&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1728&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;그리고 모델(model)들은 앱(admin, auth, contenttypes, sessions) 폴더에 있는 models.py 파일에 있습니다. (messages, staticfiles 앱에는 모델이 없습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc0G1L/btsKucIOIZt/4KO5hEXzQfK0P08UArr1oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc0G1L/btsKucIOIZt/4KO5hEXzQfK0P08UArr1oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc0G1L/btsKucIOIZt/4KO5hEXzQfK0P08UArr1oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc0G1L%2FbtsKucIOIZt%2F4KO5hEXzQfK0P08UArr1oK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1728&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개의 앱에 있는 모델 클래스가 18개 있습니다. 그래서 마이그레이션 대상이 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;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션에 대한 내용 아래에서 자세히 다루겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 데이터베이스는 어디서 설정해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 대한 설정은 config 폴더에 있는 settings.py 파일에서 DATABASES에 SQLite3로 설정되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4c1DG/btsKkJf3Vo5/9EnaHRsIH5Ig6DFmVhXpIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4c1DG/btsKkJf3Vo5/9EnaHRsIH5Ig6DFmVhXpIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4c1DG/btsKkJf3Vo5/9EnaHRsIH5Ig6DFmVhXpIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4c1DG%2FbtsKkJf3Vo5%2F9EnaHRsIH5Ig6DFmVhXpIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왼쪽 EXPLORER를 보면 db.sqlite3 파일이 추가된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 추가된 db.sqlite3 파일이 데이터베이스로 사용될 파일입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SQLite3는 Python에 포함되어 있어 별도로 설치할 필요가 없습니다. 그래서 개발할 때 편리하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 데이터베이스를 MariaDB, MySQL, PostgreSQL로 변경하실 경우에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;settings.py 파일에서 DATABASES를 변경하고 싶은 데이터베이스로 변경하시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션을 통해 데이터베이스에 관리자 테이블들을 생성해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션을 하기 위해서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt; 파일(&lt;/span&gt;&lt;/span&gt;마이그레이션 클래스가 있는 파일)을 생성해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이그레이션 클래스는 앱(app)에 모델(model)이 추가되거나 변경되었을 때 데이터베이스에 테이블을 추가, 수정, 삭제하거나 테이블의 컬럼을 추가, 수정, 삭제할 수 있게 해 줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델이 추가되면 migrations.CreateModel&lt;/li&gt;
&lt;li&gt;모델이 수정되면 migrations.AlterModelTable&lt;/li&gt;
&lt;li&gt;모델이&amp;nbsp;삭제되면&amp;nbsp;migrations.DeleteModel&lt;/li&gt;
&lt;li&gt;모델에 필드가 추가되면 migrations.AddField&lt;/li&gt;
&lt;li&gt;모델에 필드가 수정되면 migrations.AlterField&lt;/li&gt;
&lt;li&gt;모델에 필드가 삭제되면 migrations.RemoveField&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이그레이션 클래스에 대한 더 자세한 내용 장고 문서 사이트를 참고하시기 바랍니다. (&lt;a href=&quot;https://docs.djangoproject.com/en/5.1/ref/migration-operations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.djangoproject.com/en/5.1/ref/migration-operations/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 모델이 추가되면 마이그레이션 클래스의 오퍼레이션에 migrations.CreateModel 클래스로 테이블과 테이블의 필드를 추가할 수 있습니다. (admin의 LogEntry 모델에 대한 마이그레이션 파일입니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JkTxY/btsKlzp5qyi/vyXBK2ZZcH4wGOCv10KK00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JkTxY/btsKlzp5qyi/vyXBK2ZZcH4wGOCv10KK00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JkTxY/btsKlzp5qyi/vyXBK2ZZcH4wGOCv10KK00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJkTxY%2FbtsKlzp5qyi%2FvyXBK2ZZcH4wGOCv10KK00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;예를 들어, 모델이 수정되면&lt;span&gt; 마이그레이션 클래스의 오퍼레이션에&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; migrations. AlterField 클래스로 테이블의 필드를 수정할 수 있습니다. (auth의&lt;span&gt; Permission&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모델에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;마이그레이션 파일입니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T8I8r/btsKkcpo0vX/lYr4yllEospjDe9AbD3Kzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T8I8r/btsKkcpo0vX/lYr4yllEospjDe9AbD3Kzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T8I8r/btsKkcpo0vX/lYr4yllEospjDe9AbD3Kzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT8I8r%2FbtsKkcpo0vX%2FlYr4yllEospjDe9AbD3Kzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앱(App)에 모델(Model)이 있으면 반드시 모델에 대한 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클래스가&lt;span&gt;&lt;span&gt; 있어야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하나의 마이그레이션 파일에는 앱에 있는 모델 수만큼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;클래스가 있어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;그럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클래스를 직접 만들어야 할까요?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아닙니다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;직접 만들 수 있지만 장고에서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;클래스를 자동으로 생성해 주는 makemigrations 명령어를 지원하고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; makemigrations&lt;/b&gt;(메이크 마이그레이션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클래스를&lt;span&gt; &lt;/span&gt;&lt;/span&gt;생성해&lt;span&gt; &lt;/span&gt;&lt;/span&gt;주는 명령어인 makemigrations를 터미널(Terminal)에서 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729926014861&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py makemigrations {앱 이름}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(앱 이름}은 모델이 변경된 앱의 이름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(앱 이름}이 있으면 해당 앱에서만 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;추가되거나&lt;span&gt; &lt;/span&gt;&lt;/span&gt;변경된 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모델이&lt;span&gt; &lt;/span&gt;&lt;/span&gt;있는지 확인해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션 클래스를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;생성해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(앱 이름}이 없으면 전체 앱을 대상으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;추가되거나&lt;span&gt; &lt;/span&gt;&lt;/span&gt;변경된 모델이 있는지 확인해서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션 클래스를 &lt;/span&gt;생성해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고 관리자의 앱(admin, auth, contenttypes, sessions)에 대해 하나씩 실행할 수 있지만 전체 앱을 대상으로 실행하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729929747378&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py makemigrations&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2644&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QFU4F/btsKlPFV4Gv/vAPPgm3j4Qr4kwaUxDUef1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QFU4F/btsKlPFV4Gv/vAPPgm3j4Qr4kwaUxDUef1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QFU4F/btsKlPFV4Gv/vAPPgm3j4Qr4kwaUxDUef1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQFU4F%2FbtsKlPFV4Gv%2FvAPPgm3j4Qr4kwaUxDUef1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2644&quot; height=&quot;1720&quot; data-origin-width=&quot;2644&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; makemigrations를 실행하면 터미널(Terminal)에 No changes detected (감지된 변경이 없습니다.)라고 나옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;왜 장고 관리지 앱에 대해&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;클래스가 있는 파일이 생성되지 않았을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt; 파일을&lt;/span&gt;&lt;/span&gt;&amp;nbsp;생성한 적이 없는 왜 변경된 게 없다고 할까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그 이유는 이미 장고 관리자 앱에는&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;클래스가 있는 파일이 생성되어 있고 &lt;/span&gt;우리가 관리자 앱에 있는 모델을 수정한 적이 없기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위에서 마이그레이션 클래스를 설명할 때 예시로 보여드린 화면을 보시면 django의 auth &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앱에&lt;span&gt; &lt;/span&gt;migrations 폴더에서 &lt;/span&gt;마이그레이션 파일들이 있는 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다음은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;django의&amp;nbsp;&lt;/span&gt;contenttypes 앱에 migrations 폴더에 있는 &lt;/span&gt;마이그레이션 파일입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clIYpy/btsKl3qAOhi/jENS9J8gMXcaAmUKeJO3kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clIYpy/btsKl3qAOhi/jENS9J8gMXcaAmUKeJO3kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clIYpy/btsKl3qAOhi/jENS9J8gMXcaAmUKeJO3kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclIYpy%2FbtsKl3qAOhi%2FjENS9J8gMXcaAmUKeJO3kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&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;위와 같이 앱에 모델이 있을 경우 makemigrations를 실행하면 앱에 있는 migrations 폴더에 마이그레이션 파일이 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, test라는 앱을 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션 한다면 test 앱에 있는 migrations 폴더에 0001_initial.py 파일이 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730015200262&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py makemigrations test&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1730015519505&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test/ --&amp;gt; 앱 폴더
    migrations/ --&amp;gt; 마이그레이션 폴더 (앱 생성시 자동으로 생성됩니다.)
        __init__.py --&amp;gt; 초기화 파일 (파일에 내용은 없습니다. 앱 생성시 자동으로 생성됩니다.)
        0001_initial.py --&amp;gt; 처음 생성된 마이그레이션 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__init__.py 파일은 마이그레이션을 초기화하기 위해서 생성되는 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생선 된 마이그레이션 파일 명을 보면 앞에 0001_처럼 번호가 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 번호는 makemigrations를 실행할 때 앱의 모델이 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;추가되거나&lt;span&gt; &lt;/span&gt;&lt;/span&gt;변경된 것이 있으면 자동으로 번호가 증가하여 마이그레이션 파일 명에 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼, auth 앱은 처음 마이그레이션(0001_initial.py) 한 후 11번을 수정(0012_alter_user_first_name_max_length.py) 한 것을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVQDX/btsKk7VbSVn/FQyQZ6r6YF7RnNuJK7rRE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVQDX/btsKk7VbSVn/FQyQZ6r6YF7RnNuJK7rRE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVQDX/btsKk7VbSVn/FQyQZ6r6YF7RnNuJK7rRE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVQDX%2FbtsKk7VbSVn%2FFQyQZ6r6YF7RnNuJK7rRE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 번호는 또한 마이그레이션을 이전 상태로 변경하고 싶을 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 생성된&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션 파일을 통해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션을 실행해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Migrate&lt;/b&gt;(마이그레이트)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션을 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;실행&lt;/span&gt;&lt;/span&gt;해&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;주는 명령어인 migrate를 터미널(Terminal)에서 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730017128409&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py migrate {앱 이름} {마이그레이션 번호}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(앱 이름}은 마이그레이션을 할 앱의 이름입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(앱 이름}이 있으면 해당 앱에서만 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션을 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(앱 이름}이 없으면 전체 앱을 대상으로 마이그레이션을 실행합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(마이그레이션 번호}는 마이그레이션을 할 번호입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(마이그레이션 번호}가 있으면 번호로 만들어진 마이그레이션 파일을 실행합니다. (Reversing Migrations - 이전 번호를 입력하면 이전 상태로 되돌아가게 됩니다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(마이그레이션 번호}가 없으면&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;전체&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;마이그레이션 파일을 실행합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Reversing Migrations는 아래에서 설명드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 처음 실행하기 때문에 (앱 이름}과 (마이그레이션 번호}를 입력하지 않고 실행하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730017659246&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py migrate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx4diH/btsKlZu5RKT/DRNSDNerawUn9k4Jt8KtdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx4diH/btsKlZu5RKT/DRNSDNerawUn9k4Jt8KtdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx4diH/btsKlZu5RKT/DRNSDNerawUn9k4Jt8KtdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx4diH%2FbtsKlZu5RKT%2FDRNSDNerawUn9k4Jt8KtdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&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;실행 결과를 보면 장고 관리자의 앱(admin, auth, contenttypes, sessions)에 있는 마이그레이션 파일들이 실행된 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBeaver로 db.sqlite3 파일을 오픈해 보면 장고 관리자 테이블들이 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2702&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI3wbC/btsKmpmFY8A/GowkgSl55u9VuiGR48fze1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI3wbC/btsKmpmFY8A/GowkgSl55u9VuiGR48fze1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI3wbC/btsKmpmFY8A/GowkgSl55u9VuiGR48fze1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI3wbC%2FbtsKmpmFY8A%2FGowkgSl55u9VuiGR48fze1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2702&quot; height=&quot;1536&quot; data-origin-width=&quot;2702&quot; data-origin-height=&quot;1536&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;&lt;b&gt; Reversing Migrations&lt;/b&gt;(리버싱 마이그레이션, 마이그레이션을 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이전 상태로 되돌리기&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다음처럼, test앱에서 3번의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;마이그레이션을 실행했다고 가정하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730622111827&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test/
    migrations/
        __init__.py
        0001_initial.py
        0002_alter_user_name_max_length.py
        0003_alter_user_email_max_length.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 모델에 있는 메일의 길이가 수정할 필요가 없어졌다면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델에 있는 메일의 길이를 다시 이전 길이로 수정하고 다시 makemigrations를 실행하고 migrate를 실행합니다. (그럼 0004 마이그레이션 파일이 생성되고 실행됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 이전 마이그레이션(0002)으로 되돌리고 0003 마이그레이션을 삭제합니다.&lt;/p&gt;
&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;여기서는 이전 마이그레이션(0002)으로 되돌리고 0003 마이그레이션을 삭제해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이그레이션을 0002번째로 되돌리기 위해서는 migrate를 실행할 때 test 앱과 0002 번호를 입력하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730622139561&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py migrate test 0002&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 0002번째 마이그레이션으로 변경됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730622181075&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Operations to perform:
  Target specific migration: 0002_alter_user_name_max_length, from test
Running migrations:
  Rendering model states... DONE
  Unapplying test.0003_alter_user_email_max_length... OK&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 나서 0003_alter_user_email_max_length.py 파일을 삭제하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마이그레이션 파일을 삭제할 때는 주의를 기울여야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜냐하면, 마이그레이션 파일은 이전 번호 마이그레이션에 의존하게 되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 중간에 있는 마이그레이션 파일은 임의로 삭제하면 안 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다음처럼, auth 앱에 마지막 마이그레이션 파일을 보게 되면 dependencies에 &quot;0011_update_proxy_permissions&quot;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;의존하고 있는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coFDVn/btsKldgwaCo/g8JglU8aK4JTJn7MyWKDoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coFDVn/btsKldgwaCo/g8JglU8aK4JTJn7MyWKDoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coFDVn/btsKldgwaCo/g8JglU8aK4JTJn7MyWKDoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoFDVn%2FbtsKldgwaCo%2Fg8JglU8aK4JTJn7MyWKDoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2648&quot; height=&quot;1720&quot; data-origin-width=&quot;2648&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반드시 이전 번호로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Reversing Migrations 한 후에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;마이그레이션 파일을 삭제하시길 바랍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마이그레이션에 대한 더 자세한 내용 장고 문서 사이트를 참고하시기 바랍니다. (&lt;a href=&quot;https://docs.djangoproject.com/en/5.1/topics/migrations/&quot;&gt;https://docs.djangoproject.com/en/5.1/topics/migrations/&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 테스트로 auth 앱의 마이그레이션을 0011번으로 Reversing Migrations 한 후에 다시 0012번으로 마이그레이션 해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730625495557&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py migrate auth 0011&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciOW9f/btsKujHMjnP/O1BO0pdRMzKQQOhNUB8tK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciOW9f/btsKujHMjnP/O1BO0pdRMzKQQOhNUB8tK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciOW9f/btsKujHMjnP/O1BO0pdRMzKQQOhNUB8tK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciOW9f%2FbtsKujHMjnP%2FO1BO0pdRMzKQQOhNUB8tK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1728&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730625502862&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py migrate auth 0012&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FLLz3/btsKvB8omtZ/SKWopRBU2HOSM9uJ6Ojnbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FLLz3/btsKvB8omtZ/SKWopRBU2HOSM9uJ6Ojnbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FLLz3/btsKvB8omtZ/SKWopRBU2HOSM9uJ6Ojnbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFLLz3%2FbtsKvB8omtZ%2FSKWopRBU2HOSM9uJ6Ojnbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1728&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>django</category>
      <category>django</category>
      <category>makemigrations</category>
      <category>Migrate</category>
      <category>reversing migrations</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/307</guid>
      <comments>https://carrotweb.tistory.com/307#entry307comment</comments>
      <pubDate>Sun, 27 Oct 2024 22:21:31 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 장고 설치 및 프로젝트 생성, 실행 - Installing Django, django-admin startproject, manage.py runserver</title>
      <link>https://carrotweb.tistory.com/306</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;장고(Django)는 하이 레벨 파이썬 웹 프레임워크(high-level&amp;nbsp;Python&amp;nbsp;web&amp;nbsp;framework)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발에 필요한 구성과 환경 설정들을 프레임워크가 처리해 주기 때문에 개발에 더 집중할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 무료이며 오픈 소스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jlml0/btsKbI9eUNf/8rb34WMMXpNoUaaBPMUA9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jlml0/btsKbI9eUNf/8rb34WMMXpNoUaaBPMUA9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jlml0/btsKbI9eUNf/8rb34WMMXpNoUaaBPMUA9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJlml0%2FbtsKbI9eUNf%2F8rb34WMMXpNoUaaBPMUA9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1638&quot; height=&quot;406&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;406&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;장고(Django)는 프런트엔드(사용자 대면)와 백엔드(데이터베이스 및 로직)를 모두 개발할 수 있는 풀 스택 프레임워크(Full Stack Framework)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django에서 D가 묵음이기 때문에 &quot;드장고&quot;가 아닌 &quot;장고&quot;로 읽으시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 내용 장고 사이트를 참고하시기 바랍니다. (&lt;a href=&quot;https://www.djangoproject.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.djangoproject.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2392&quot; data-origin-height=&quot;1642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sp65v/btsKdDL1I9E/FK91iNoi4A6MwY52qbZDUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sp65v/btsKdDL1I9E/FK91iNoi4A6MwY52qbZDUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sp65v/btsKdDL1I9E/FK91iNoi4A6MwY52qbZDUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSp65v%2FbtsKdDL1I9E%2FFK91iNoi4A6MwY52qbZDUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2392&quot; height=&quot;1642&quot; data-origin-width=&quot;2392&quot; data-origin-height=&quot;1642&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;&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;현재 가상 환경에 장고가 설치되어 있는지 확인하기 위해서 Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729351144292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python -m django --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Afez2/btsKdMoAQcc/xkXOfcWlJ8vXgjIbV8jJT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Afez2/btsKdMoAQcc/xkXOfcWlJ8vXgjIbV8jJT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Afez2/btsKdMoAQcc/xkXOfcWlJ8vXgjIbV8jJT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAfez2%2FbtsKdMoAQcc%2FxkXOfcWlJ8vXgjIbV8jJT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&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;No module named django(장고라는 모듈이 없습니다.)&quot;라고 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 가상 환경에 장고를 설치해 보겠습니다.&lt;/p&gt;
&lt;p 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;&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;장고를 설치하기 위해 Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729350577652&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install django=={버전}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 작성 당시 버전은 5.1.2입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729351044825&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install Django==5.1.2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전을 입력하지 않으면 현재 최신 버전으로 설치됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729350775653&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install django&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고는 다음처럼 버전에 대한 지원 연도를&amp;nbsp;알려주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 특정 연도에 지원이 되지 않는 버전을 사용하고 있을 경우 장고 버전을 업그레이드해야 합니다.&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;1630&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3HYSS/btsKb5JMJH6/lVWLFjsYkQrYgoYJteIlwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3HYSS/btsKb5JMJH6/lVWLFjsYkQrYgoYJteIlwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3HYSS/btsKb5JMJH6/lVWLFjsYkQrYgoYJteIlwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3HYSS%2FbtsKb5JMJH6%2FlVWLFjsYkQrYgoYJteIlwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;768&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;768&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;a href=&quot;https://www.djangoproject.com/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.djangoproject.com/download/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 최신 버전을 설치하기 위해 버전을 입력하지 않았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729351953397&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install django&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LrHkk/btsKccovZg4/LxfZ3swIPjJe1PGKpdwH61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LrHkk/btsKccovZg4/LxfZ3swIPjJe1PGKpdwH61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LrHkk/btsKccovZg4/LxfZ3swIPjJe1PGKpdwH61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLrHkk%2FbtsKccovZg4%2FLxfZ3swIPjJe1PGKpdwH61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&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;장고를 설치한 후 다시 버전을 확인해 보면 5.1.2이 설치된 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 가상 환경 폴더(myenv)의 Lib 폴더를 보면 장고(django)와 관련 모듈(asgiref, sqlparse, tzdata)들이 설치되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NcnxU/btsKcNort4o/OeoDKFdiG0Oy72JAn86EO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NcnxU/btsKcNort4o/OeoDKFdiG0Oy72JAn86EO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NcnxU/btsKcNort4o/OeoDKFdiG0Oy72JAn86EO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNcnxU%2FbtsKcNort4o%2FOeoDKFdiG0Oy72JAn86EO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 장고를 기반으로 하는 프로젝트를 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로젝트 관리 파일(manage.py)과 프로젝트 패키지 파일(settings.py, urls.py, asgi.py, wsgi.py)들을 생성하기 위해 &lt;/span&gt;Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729420497385&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;django-admin startproject {이름} {디렉토리}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;django-admin은 장고를 관리하는 명령입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729423036583&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;django-admin &amp;lt;command&amp;gt; [options]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;django-admin startproject 명령은 &lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt;프로젝트가 구동될 수 있게 구조를 생성해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{이름}은 프로젝트 패키지 파일들이 들어갈 폴더 이름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt;{디렉토리}는 프로젝트를 생성할 디렉터리입니다. (현재 &lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt;디렉토리&lt;/span&gt;에서 생성할 경우 &quot;.&quot;를 입력하고 다른 &lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt;디렉토리&lt;/span&gt;로 생성할 경우 &lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt;디렉토리&lt;/span&gt;를 입력하면 됩니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;더 자세한 내용 장고 문서 사이트를 참고하시기 바랍니다.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;(&lt;a href=&quot;https://docs.djangoproject.com/en/5.1/ref/django-admin/#startproject&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.djangoproject.com/en/5.1/ref/django-admin/#startproject&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 현재 폴더에 &lt;span style=&quot;background-color: #ffffff; color: #0c3c26; text-align: start;&quot;&gt;프로젝트 구조를 생성할 것이 때문에 &quot;.&quot;를 입력하였고 프로젝트 패키지 파일들이 들어갈 폴더 이름은 &quot;config&quot;으로 입력하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729420184258&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;django-admin startproject config .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1HAl8/btsKb027nrZ/ajkGA3MZK9o69dziRyvDN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1HAl8/btsKb027nrZ/ajkGA3MZK9o69dziRyvDN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1HAl8/btsKb027nrZ/ajkGA3MZK9o69dziRyvDN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1HAl8%2FbtsKb027nrZ%2FajkGA3MZK9o69dziRyvDN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1716&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;EXPLORER를 보면 PROJECT1 폴더에 config 폴더(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로젝트 패키지 파일들이 있는 폴더&lt;/span&gt;)와 manage.py(프로젝트 관리 파일)가 생성된 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 프로젝트를 실행해 보도록 하겠습니다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&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;&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. 파이썬에서 제공하는 개발 웹 서버로 프로젝트를 실행하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729422813424&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py runserver&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m3naQ/btsKcXxOz0X/cL76hB753AXsHVmYgqviI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m3naQ/btsKcXxOz0X/cL76hB753AXsHVmYgqviI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m3naQ/btsKcXxOz0X/cL76hB753AXsHVmYgqviI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm3naQ%2FbtsKcXxOz0X%2FcL76hB753AXsHVmYgqviI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1716&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1716&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;개발 웹 서버는 로컬 컴퓨터에서 실행되기 때문에 도메인이 127.0.0.1로 실행되고 포트를 입력하지 않으면 자동으로 8000으로 설정되어 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;더 자세한 내용 장고 문서 사이트를 참고하시기 바랍니다.&lt;/span&gt; (&lt;a href=&quot;https://docs.djangoproject.com/en/5.1/ref/django-admin/#runserver&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.djangoproject.com/en/5.1/ref/django-admin/#runserver&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 웹 브라우저에서 &quot;http://127.0.0.1:8000/&quot;를 입력합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvVu4n/btsKcfS58Om/rfhMDpdu8UysKBVNkpF5W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvVu4n/btsKcfS58Om/rfhMDpdu8UysKBVNkpF5W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvVu4n/btsKcfS58Om/rfhMDpdu8UysKBVNkpF5W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvVu4n%2FbtsKcfS58Om%2FrfhMDpdu8UysKBVNkpF5W0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2450&quot; height=&quot;1618&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고 프로젝트에서 제공하는 기본 페이지가 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>django</category>
      <category>django-admin</category>
      <category>install</category>
      <category>manage.py</category>
      <category>runserver</category>
      <category>startproject</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/306</guid>
      <comments>https://carrotweb.tistory.com/306#entry306comment</comments>
      <pubDate>Sun, 20 Oct 2024 20:59:40 +0900</pubDate>
    </item>
    <item>
      <title>윈도우 파이썬 가상 환경 생성 - Python venv - Creation of virtual environments</title>
      <link>https://carrotweb.tistory.com/305</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 프로젝트 별로 파이썬 환경이 독립될 수 있도록 가상 환경(virtual environments)을 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 환경을 생성하면 프로젝트 별로 다른 라이브러리와 버전을 다르게 설치하고 관리할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 가상 환경을 생성해야 할까요? 그냥 필요한 라이브러리를 설치하면 안 될까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로젝트들이 동일한 환경에서 개발되면 문제가 없겠지만 여러 프로젝트들이 다르게 사용하는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;라이브러리가 있거나&lt;span&gt; 다른 라이브러리 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;버전이 있을 경우에는 문제 됩니다. 그래서, 프로젝트 별로 가상 환경을 생성하고 가상 환경에 라이브러리를 설치하면 이런 문제점들을 해결할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 내용은 파이썬 문서를 참고하시기 바랍니다. (&lt;a href=&quot;https://docs.python.org/3/library/venv.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.python.org/3/library/venv.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(python 사이트 상단에 있는 Docs 클릭 &amp;gt; Python 3.12.7 documentation에서 Library reference 클릭 &amp;gt; The Python Standard Library에서 Software Packaging and Distribution에 있는 venv &amp;mdash; Creation of virtual environments 클릭)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2578&quot; data-origin-height=&quot;1830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2WvKl/btsJU21Byjq/K940fbbTck24kPYZjMXfY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2WvKl/btsJU21Byjq/K940fbbTck24kPYZjMXfY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2WvKl/btsJU21Byjq/K940fbbTck24kPYZjMXfY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2WvKl%2FbtsJU21Byjq%2FK940fbbTck24kPYZjMXfY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2578&quot; height=&quot;1830&quot; data-origin-width=&quot;2578&quot; data-origin-height=&quot;1830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;가상 환경은 파이썬 3.3에 추가된 기능입니다. (파이썬 3.3.0은 2012년 9월 29일에 릴리즈 되었기 때문에 오래전에 설치하지 않았다면 사용하는 데는 문제가 없을 겁니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치된 파이썬의 버전을 확인하기 위해서는 콘솔에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728108627172&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lT4vZ/btsJVT3KeSP/PhFA90n971y1sZqjUghBSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lT4vZ/btsJVT3KeSP/PhFA90n971y1sZqjUghBSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lT4vZ/btsJVT3KeSP/PhFA90n971y1sZqjUghBSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlT4vZ%2FbtsJVT3KeSP%2FPhFA90n971y1sZqjUghBSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2350&quot; height=&quot;1226&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Python 3.12.4가 설치되어 있습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(파이썬 저장 위치 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;C:\workspaces\Python\python312)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 PC에 설치된 파이썬 라이브러리를 확인하기 위해서는 콘솔에서 다음과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728108936880&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X6vCe/btsJUWAl9WV/hTFgY0vS5hEeCYai7WiGd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X6vCe/btsJUWAl9WV/hTFgY0vS5hEeCYai7WiGd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X6vCe/btsJUWAl9WV/hTFgY0vS5hEeCYai7WiGd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX6vCe%2FbtsJUWAl9WV%2FhTFgY0vS5hEeCYai7WiGd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2350&quot; height=&quot;1226&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 PC에는 다른 라이브러리가 설치되어 있지 않습니다. (pip 버전이 24.2로 업그레이드되었다고 알려주네요. 아래에 있는 명령어를 실행하여 업그레이드를 해야겠습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip는 파이썬 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;패키지를 설치하고 관리해 주는 패키지&lt;/span&gt;&amp;nbsp;관리&amp;nbsp;시스템입니다. (&lt;a href=&quot;https://pypi.org/project/pip/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pypi.org/project/pip/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip는 파이썬 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;설치 프로그램에서 선택하여 설치할 수 있습니다. (기본적으로 pip는 체크되어 있습니다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSpIIi/btsJWOgi8C1/axBKm0ZspSMgcY8dareQFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSpIIi/btsJWOgi8C1/axBKm0ZspSMgcY8dareQFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSpIIi/btsJWOgi8C1/axBKm0ZspSMgcY8dareQFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSpIIi%2FbtsJWOgi8C1%2FaxBKm0ZspSMgcY8dareQFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;370&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;810&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;가상 환경은 콘솔에서 생성할 수 있습니다. 그러나 저는 새로운 프로젝트를 위해 폴더를 생성하고 프로젝트에서 사용할 가상 환경을 생성하도록 하겠습니다. 그리고 개발 IDE로 Visual Studio Code를 사용하겠습니다.&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;가상 환경 생성&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;1. 프로젝트 폴더를 생성합니다. (저는 C:\workspaces\projects\project1 폴더를 생성하였습니다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Visual Studio Code의 메뉴 File(파일) &amp;gt; Open Folder...(폴더 열기)를 클릭하여 &quot;C:\workspaces\projects\project1&quot; 폴더로 이동하고 &quot;폴더 선택&quot; 버튼을 누르면 폴더가 오픈됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2490&quot; data-origin-height=&quot;1688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFloSX/btsJVWGanWD/3UF3rr9CZdKA39NUWgkVik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFloSX/btsJVWGanWD/3UF3rr9CZdKA39NUWgkVik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFloSX/btsJVWGanWD/3UF3rr9CZdKA39NUWgkVik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFloSX%2FbtsJVWGanWD%2F3UF3rr9CZdKA39NUWgkVik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2490&quot; height=&quot;1688&quot; data-origin-width=&quot;2490&quot; data-origin-height=&quot;1688&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;보안을 위해 Trust(신뢰) 확인 창이 나타납니다. &quot;Yes, I Trust the authors&quot; 버튼을 누릅니다. 그리고 Welcome 창은 닫습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Visual Studio Code의 메뉴 Terminal(터미널) &amp;gt; New Terminal(새 터미널)를 클릭합니다. 그리고 가상 환경을 생성하기 위해 Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728111660631&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python -m venv {가상 환경 이름 또는 경로를 포함한 가상 환경 이름}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 가상 환경 이름을 myenv(my environments)라고 하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728111295342&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python -m venv myenv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbZ2n/btsKddfR74f/OiEYhXKF2BjjYHwTLUMEo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbZ2n/btsKddfR74f/OiEYhXKF2BjjYHwTLUMEo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbZ2n/btsKddfR74f/OiEYhXKF2BjjYHwTLUMEo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbZ2n%2FbtsKddfR74f%2FOiEYhXKF2BjjYHwTLUMEo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&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;가상 환경이 생성되면 project1 폴더에 myenv라는 폴더가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 환경 폴더 구조는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728112311879&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;project1/
    myenv/ --&amp;gt; 가상 환경 풀더
        Lib --&amp;gt; 가상 환경에서 pip로 설치한 라이브러리들이 저장되는 폴더
        Scripts --&amp;gt; 가상 환경을 관리하는 스크립트가 있는 폴더&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 생성된 가상 환경을 활성화하기 위해서는 myenv의 Scripts 폴더에 있는 activate를 Terminal(터미널)에서&lt;span&gt; &lt;/span&gt;실행해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728112873308&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd myenv\Scripts                                                               
./activate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oSqsi/btsKdC7sECG/ObktNTlkkSvvlOEN1Nacf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oSqsi/btsKdC7sECG/ObktNTlkkSvvlOEN1Nacf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oSqsi/btsKdC7sECG/ObktNTlkkSvvlOEN1Nacf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoSqsi%2FbtsKdC7sECG%2FObktNTlkkSvvlOEN1Nacf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&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;가상 환경이 활성화되면 가상 환경 이름인 (myenv)가 표시됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728113170364&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(myenv) PS C:\workspaces\projects\project1\myenv\Scripts&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로운 프로젝트를 위한 가상 환경이 생성되었습니다. (가상 환경이 활성화되어 있어야 pip로 설치한 라이브러리들이 가상 환경의 Lib 폴더에 저장이 됩니다.)&lt;/p&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;Python 3.12.4인 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 설치된 파이썬 라이브러리를 확인하면 pip가 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3mmcc/btsKcOnnCij/z3iZ9wkrzgfCZheId81BKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3mmcc/btsKcOnnCij/z3iZ9wkrzgfCZheId81BKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3mmcc/btsKcOnnCij/z3iZ9wkrzgfCZheId81BKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3mmcc%2FbtsKcOnnCij%2Fz3iZ9wkrzgfCZheId81BKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가상 환경을 비활성화하기 위해서는 myenv의 Scripts 폴더에 있는 deactivate를 Terminal(터미널)에서&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실행해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728113118126&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;deactivate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xOyvT/btsKcRYyUMh/w2wKy7vt3OE7AmhCMi1YxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xOyvT/btsKcRYyUMh/w2wKy7vt3OE7AmhCMi1YxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xOyvT/btsKcRYyUMh/w2wKy7vt3OE7AmhCMi1YxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxOyvT%2FbtsKcRYyUMh%2Fw2wKy7vt3OE7AmhCMi1YxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2632&quot; height=&quot;1712&quot; data-origin-width=&quot;2632&quot; data-origin-height=&quot;1712&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;&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;저의 PC에는 다른 파이썬 버전인 3.11.1도 설치되어 있습니다. (파이썬 저장 위치 : C:\workspaces\Python\python311)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프로젝트 폴더를 생성합니다. (저는 C:\workspaces\projects\project2 폴더를 생성하였습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Visual Studio Code의 메뉴 File(파일) &amp;gt; Open Folder...(폴더 열기)를 클릭하여 &quot;C:\workspaces\projects\project2&quot; 폴더를 오픈하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Visual Studio Code의 메뉴 Terminal(터미널) &amp;gt; New Terminal(새 터미널)를 클릭하고 가상 환경을 생성하기 위해 Terminal(터미널)에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728200326495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;py -{파이썬 버전} -m venv myenv&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 버전의 파이썬이 설치되어 있으면 python 명령어 대신 py 명령어를 사용하여 버전 별로 실행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 3.11 버전으로 가상 환경을 생성하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728113855973&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;py -3.11 -m venv myenv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;1464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csRB4M/btsJVbK256I/ayPObrcSVBMwnfvrFjxEtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csRB4M/btsJVbK256I/ayPObrcSVBMwnfvrFjxEtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csRB4M/btsJVbK256I/ayPObrcSVBMwnfvrFjxEtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsRB4M%2FbtsJVbK256I%2FayPObrcSVBMwnfvrFjxEtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2234&quot; height=&quot;1464&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;1464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 생성된 가상 환경을 활성화하기 위해서는 myenv의 Scripts 폴더에 있는 activate를 Terminal(터미널)에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실행했습니다. 그리고 가상 환경의 파이썬의 버전을 확인해 보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;1464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XgEDu/btsJWzcApbr/zHulpethLax0CFXR5h5w21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XgEDu/btsJWzcApbr/zHulpethLax0CFXR5h5w21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XgEDu/btsJWzcApbr/zHulpethLax0CFXR5h5w21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXgEDu%2FbtsJWzcApbr%2FzHulpethLax0CFXR5h5w21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2234&quot; height=&quot;1464&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;1464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 환경이 다른 파이썬 버전으로 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;py 명령어 대신 다른 파이썬 버전이 설치되어 있는 경로를 이용해서 가상 환경을 생성할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728116903523&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\workspaces\projects\project2&amp;gt; C:\workspaces\Python\python311\python.exe -m venv myenv&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;py 명령어&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;여러 버전의 파이썬이 설치되어 있을 경우 py 명령어를 사용하여 버전 별로 파이썬을 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;콘솔에서 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728206257011&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;py -h&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bknyMB/btsJWvH4EXh/162d3pSfVJmJFnpL3SR1s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bknyMB/btsJWvH4EXh/162d3pSfVJmJFnpL3SR1s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bknyMB/btsJWvH4EXh/162d3pSfVJmJFnpL3SR1s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbknyMB%2FbtsJWvH4EXh%2F162d3pSfVJmJFnpL3SR1s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2350&quot; height=&quot;1226&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치되어 있는 버전으로 파이썬을 실행하기 위해서는 launcher-args(Launcher arguments)에 실행할 버전을 입력하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728206469385&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;py [launcher-args]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치되어 있는 파이썬 버전인 3.11.1과 3.12.4를 py 명령어를 이용하여 실행하고 3.X 버전 중 마지막 버전으로 실행되게 하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728206723893&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;py -3.11
py -3.12
py -3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzqL5c/btsJWuvEniQ/IHPQhDjPmWKNkpjkkfp6Z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzqL5c/btsJWuvEniQ/IHPQhDjPmWKNkpjkkfp6Z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzqL5c/btsJWuvEniQ/IHPQhDjPmWKNkpjkkfp6Z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzqL5c%2FbtsJWuvEniQ%2FIHPQhDjPmWKNkpjkkfp6Z1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2350&quot; height=&quot;1226&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>django</category>
      <category>PIP</category>
      <category>PY</category>
      <category>Python</category>
      <category>venv</category>
      <category>가상환경</category>
      <category>파이썬</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/305</guid>
      <comments>https://carrotweb.tistory.com/305#entry305comment</comments>
      <pubDate>Sun, 6 Oct 2024 18:28:31 +0900</pubDate>
    </item>
    <item>
      <title>three.js 3D 모델을 glTF 파일로 다운로드하기(.glb or .gltf)</title>
      <link>https://carrotweb.tistory.com/304</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 추출된 glTF 정보를 .glb 파일(바이너리) 또는 .gltf&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;파일(JSON)&lt;/span&gt;로 다운로드하여 저장할 수 있게 헤보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;glTF 정보를 스크립트에서 다운로드하기 위해서는 URL.createObjectURL() 메서드를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL.createObjectURL() 메서드에 대한 영문 설명은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The&amp;nbsp;URL.createObjectURL()&amp;nbsp;static&amp;nbsp;method&amp;nbsp;creates&amp;nbsp;a&amp;nbsp;string&amp;nbsp;containing&amp;nbsp;a&amp;nbsp;URL&amp;nbsp;representing&amp;nbsp;the&amp;nbsp;object&amp;nbsp;given&amp;nbsp;in&amp;nbsp;the&amp;nbsp;parameter.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역해 보면 URL.createObjectURL() 정적 메서드는 파라미터로 전달받은 객체를 가리키는 URL이 포함된 문자열을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697363134375&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;URL.createObjectURL(object)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;object: File, Blob, MediaSource 객체입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;File 객체 또는 Blob 객체 또는 MediaSource 객체를 URL로 접근할 수 있게 아래와 같이 Blob URL를 생성하여 리턴하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697367065526&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;blob:http://localhost:5173/c95eb15c-3c2d-4ca1-b327-8e25a8bfe73e&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 URL.createObjectURL() 정적 메서드를 사용할 때마다 새로운 Blob URL를 생성되기 때문에 생성된 Blob URL은 URL.revokeObjectURL() 정적 메서드를 사용하여 해제해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 생성된 Blob URL를 해제하지 않아도 웹 브라우저가 unload(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;다른 경로로 이동&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하거나 웹 브라우저가 종료될 때)가 되면&lt;span&gt; 웹 브라우저가&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;자동으로 해제해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 URL.createObjectURL()와 URL.revokeObjectURL() 정적 메서드로 blob URL를 생성하고 해제하는 방법은 W3C의 &lt;a href=&quot;https://www.w3.org/TR/FileAPI/#creating-revoking&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Creating and Revoking a blob URL&lt;/a&gt;를 참고하시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;URL.createObjectURL() 정적 메서드로 Blob URL를 생성하기 위해서는 먼저 glTF 정보를 Blob 객체로 만들어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blob(Binary Large Object, 비랍/블랍)은 대용량의 바이너리로 저장된 객체입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697380630537&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new Blob(array, options)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;array: ArrayBuffer[] 또는 TypedArray[] 또는 DataView[] 또는 USVString(UTF-8로 인코딩 된 string[]) &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;또는 다른&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Blob 배열 객체이거나 객체 또는 여러 개의 타입이 혼합된 배열 객체입니다.&lt;/li&gt;
&lt;li&gt;options: 데이터의 type과 endings을 설정하는 옵셥입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options은 다음과 같이 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;type: 저장될 데이터의 MIME 타입입니다. 기본값 &quot;&quot;(빈 문자열)&lt;/li&gt;
&lt;li&gt;endings(transparent, native): array 타입이 USVString(UTF-8로 인코딩 된 string[])일 때&amp;nbsp; 개행 문자(&quot;\n&quot;)를 사용 중인 OS에 맞춰서 개행 문자를 변환할지(native) 아니면 변환하지 않을지(transparent) 선택합니다. 기본값 transparent&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;추출된 glTF&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정보를&lt;span&gt; Blob(Binary Large Object, 비랍/블랍)으로 만들고 Blob URL를 생성하여 파일로 다운로드해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. &lt;/span&gt;추출된 glTF 정보를 파라미터로 받아 바이너리 형식과&amp;nbsp;JSON 형식의 &amp;nbsp;Blob 객체를 만드는 함수를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이너리 형식으로 변환하기 위해서 options의 type를 &quot;application/octet-stream&quot;으로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697384331109&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 바이너리 형식으로 변환
function downloadBinary(buffer) {
    const blob = new Blob([buffer], {type: 'application/octet-stream'});
    console.log(&quot;Blob:&quot;, blob);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JSON 형식으로 변환하기 위해서&lt;span&gt;&amp;nbsp;추출된 glTF 정보를 JSON 문자열로 변환하고 &lt;/span&gt;&lt;/span&gt;options의 type를 &quot;application/json&quot;으로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697384843411&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JSON 형식으로 변환
function downloadJSON(buffer) {
    const blob = new Blob([JSON.stringify(buffer)], {type: 'application/json'});
    console.log(&quot;Blob:&quot;, blob);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/banDXv/btszlI5BJvn/pzeEtpggRzdyz4xmK0hIVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/banDXv/btszlI5BJvn/pzeEtpggRzdyz4xmK0hIVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/banDXv/btszlI5BJvn/pzeEtpggRzdyz4xmK0hIVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbanDXv%2FbtszlI5BJvn%2FpzeEtpggRzdyz4xmK0hIVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존 ExportglTF() 함수에 downloadBinary()와 downloadJSON()를 추가합니다. glTF 정보가 ArrayBuffer[]이면 downloadBinary() 함수를 호출하고 그렇지 않으면 downloadJSON() 함수를 호출하게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697385054261&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExportglTF() {
    // GLTF익스포트 생성
    const exporter = new GLTFExporter();

    // GLTF익스포트 옵션 - 바이너리 형식으로 추출
    const options = {
        binary: false
    };

    exporter.parse(
        box2,
        function (gltf) {
            if (gltf instanceof ArrayBuffer) {
                console.log(&quot;export gltf:&quot;, gltf);
                downloadBinary(gltf);
            } else {
                console.log(&quot;export gltf:&quot;, gltf);
                downloadJSON(gltf);
            }
        },
        function (error) {
            console.error(&quot;exporter error:&quot;, error);
        },
        options
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zpqUS/btszjBsWnnJ/GgoA34kYO8aztWynU6N5p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zpqUS/btszjBsWnnJ/GgoA34kYO8aztWynU6N5p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zpqUS/btszjBsWnnJ/GgoA34kYO8aztWynU6N5p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzpqUS%2FbtszjBsWnnJ%2FGgoA34kYO8aztWynU6N5p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Controls에서 &quot;Export&quot; 버튼을 클릭하면 콘솔에 Blob 객체 정보가 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHPZP7/btsyue5tMFl/a0SIGzZeZONVf24QPhyI1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHPZP7/btsyue5tMFl/a0SIGzZeZONVf24QPhyI1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHPZP7/btsyue5tMFl/a0SIGzZeZONVf24QPhyI1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHPZP7%2Fbtsyue5tMFl%2Fa0SIGzZeZONVf24QPhyI1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1638&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF&lt;span&gt; &lt;/span&gt;&lt;/span&gt;익스포트 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;옵션의 binry가 flase이면 JSON 형식으로&lt;span&gt; &lt;/span&gt;&lt;/span&gt;다음과 같이 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;콘솔에&lt;span&gt; &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;출력됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698570212468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GLTF익스포트 옵션 - JSON 형식으로 추출
const options = {
    binary: false
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eh9l6t/btsyta3Qpl5/0azuMAnUvqtDRiNEQB3r20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eh9l6t/btsyta3Qpl5/0azuMAnUvqtDRiNEQB3r20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eh9l6t/btsyta3Qpl5/0azuMAnUvqtDRiNEQB3r20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feh9l6t%2Fbtsyta3Qpl5%2F0azuMAnUvqtDRiNEQB3r20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1638&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&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;3. 파일로 다운로드하기 위해서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Blob 객체를&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;파라미터로 받는&amp;nbsp;&lt;/span&gt;다운로드 함수를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드 URL를 생성하기 위해서 URL.createObjectURL() 메서드를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697385718232&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다운로드
function download(blob) {
    const blobURL = URL.createObjectURL(blob);
    console.log(blobURL);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 downloadBinary()와 downloadJSON()에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다운로드 함수를&lt;span&gt; &lt;/span&gt;&lt;/span&gt;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1697386227589&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 바이너리 형식으로 변환
function downloadBinary(buffer) {
    const blob = new Blob([buffer], {type: 'application/octet-stream'});
    console.log(&quot;Blob:&quot;, blob);
    download(blob);
}

// JSON 형식으로 변환
function downloadJSON(buffer) {
    const blob = new Blob([JSON.stringify(buffer)], {type: 'application/json'});
    console.log(&quot;Blob:&quot;, blob);
    download(blob);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boFz7h/btszllphTPb/Ch5hAKk1QmT2e6wF06f6u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boFz7h/btszllphTPb/Ch5hAKk1QmT2e6wF06f6u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boFz7h/btszllphTPb/Ch5hAKk1QmT2e6wF06f6u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboFz7h%2FbtszllphTPb%2FCh5hAKk1QmT2e6wF06f6u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Controls에서 &quot;Export&quot; 버튼을 클릭하면 콘솔에 Blob URL이 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/le1jN/btsytKKfQ5M/EcUnNK8HGn3IhmeQ9kLk7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/le1jN/btsytKKfQ5M/EcUnNK8HGn3IhmeQ9kLk7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/le1jN/btsytKKfQ5M/EcUnNK8HGn3IhmeQ9kLk7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fle1jN%2FbtsytKKfQ5M%2FEcUnNK8HGn3IhmeQ9kLk7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1638&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1697386749440&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;blob:http://localhost:5173/b8c1b2f5-f4ee-4a9d-b286-7aed8af4dd47&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF&lt;span&gt; &lt;/span&gt;&lt;/span&gt;익스포트 옵션의 binry가 flase이면 JSON 형식으로 다음과 같이&lt;span&gt; Blob URL이 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;콘솔에&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;출력됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698570283494&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GLTF익스포트 옵션 - JSON 형식으로 추출
const options = {
    binary: false
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1gjWv/btsyCgOEEqJ/yIEKdSuWGIHZsKpKScnvDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1gjWv/btsyCgOEEqJ/yIEKdSuWGIHZsKpKScnvDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1gjWv/btsyCgOEEqJ/yIEKdSuWGIHZsKpKScnvDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1gjWv%2FbtsyCgOEEqJ%2FyIEKdSuWGIHZsKpKScnvDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1638&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1697386621410&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;blob:http://localhost:5173/b86e8950-8d45-4b8c-8822-16703dc678c2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 생성된 Blob URL로 다운로드하기 위해서 A 태그를 생성하고 href 속성 값으로 Blob URL를 설정합니다. 그리고 생성된 A 태그를 페이지에 추가하고 click() 메서드로 A 태그를 클릭하여 다운로드하게 합니다. 다운로드 후 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;생성된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Blob URL를 해제하고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;페이지에&lt;span&gt; 추가된 A 태그를 삭제합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697387146415&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다운로드
function download(blob) {
    const blobURL = URL.createObjectURL(blob);
    console.log(blobURL);
    // 다운로드를 하기위해 Blob URL로 A 태그 생성
    const link = document.createElement('a');
    link.href = blobURL;
    // 다운로드 파일 명
    link.download = &quot;test.glb&quot;;
    // A 태그 추가
    document.body.appendChild(link);
    // A 태그를 클릭하여 다운로드
    link.click();
    // 다운로드 후 Blob URL 해제
    URL.revokeObjectURL(blobURL);
    // A 태그 삭제
    document.body.removeChild(link);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7cG9N/btszkQ39LgL/SurG2xUcKUblIXWKHOzTK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7cG9N/btszkQ39LgL/SurG2xUcKUblIXWKHOzTK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7cG9N/btszkQ39LgL/SurG2xUcKUblIXWKHOzTK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7cG9N%2FbtszkQ39LgL%2FSurG2xUcKUblIXWKHOzTK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Controls에서 &quot;Export&quot; 버튼을 클릭하면 콘솔에 glTF 파일이 다운로드됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW4WeG/btsywgV1ZSO/ZEh6pMuXC2RxXL5ZvLKkrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW4WeG/btsywgV1ZSO/ZEh6pMuXC2RxXL5ZvLKkrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW4WeG/btsywgV1ZSO/ZEh6pMuXC2RxXL5ZvLKkrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW4WeG%2FbtsywgV1ZSO%2FZEh6pMuXC2RxXL5ZvLKkrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1638&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;익스포트 옵션과 &lt;/span&gt;다운로드되는 파일 명을 변경할 수 있게 처리해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;5. Controls에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;익스포트 옵션을 변경하고 다운로드 파일명을 변경할 수 있게 객체(exporter1)에 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698572655663&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const exporter1 = {
    ExportglTFFileName : 'test.glb',
    ExportglTFFilebinary : true,
    ExportglTFFunction : ExportglTF
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqboPc/btszjzaSxoL/Hjx6x8G7iulkkThFaUhtAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqboPc/btszjzaSxoL/Hjx6x8G7iulkkThFaUhtAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqboPc/btszjzaSxoL/Hjx6x8G7iulkkThFaUhtAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqboPc%2FbtszjzaSxoL%2FHjx6x8G7iulkkThFaUhtAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;익스포트 옵션의 binary를&amp;nbsp; &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;exporter1의 ExportglTFFilebinary로 설정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698573182253&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExportglTF() {
    // GLTF익스포트 생성
    const exporter = new GLTFExporter();

    // GLTF익스포트 옵션 - 바이너리/JSON 형식으로 추출
    const options = {
        binary: exporter1.ExportglTFFilebinary
    };

    exporter.parse(
        box2,
        function (gltf) {
            if (gltf instanceof ArrayBuffer) {
                console.log(&quot;export gltf:&quot;, gltf);
                downloadBinary(gltf);
            } else {
                console.log(&quot;export gltf:&quot;, gltf);
                downloadJSON(gltf);
            }
        },
        function (error) {
            console.error(&quot;exporter error:&quot;, error);
        },
        options
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7eDx8/btszoik3kyW/XKJgcwOhW1N8YKbSNPw88k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7eDx8/btszoik3kyW/XKJgcwOhW1N8YKbSNPw88k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7eDx8/btszoik3kyW/XKJgcwOhW1N8YKbSNPw88k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7eDx8%2Fbtszoik3kyW%2FXKJgcwOhW1N8YKbSNPw88k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다운로드 함수에서 다운로드 파일명을 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;exporter1의&lt;span&gt; ExportglTFFileName으&lt;/span&gt;로 설정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698573363036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다운로드
function download(blob) {
    const blobURL = URL.createObjectURL(blob);
    console.log(blobURL);
    // 다운로드를 하기위해 Blob URL로 A 태그 생성
    const link = document.createElement('a');
    link.href = blobURL;
    // 다운로드 파일 명
    link.download = exporter1.ExportglTFFileName;
    // A 태그 추가
    document.body.appendChild(link);
    // A 태그를 클릭하여 다운로드
    link.click();
    // 다운로드 후 Blob URL 해제
    URL.revokeObjectURL(blobURL);
    // A 태그 삭제
    document.body.removeChild(link);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P0FZ0/btszrLtwxrX/Uo9vWbEdUiWFz59dYk7dWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P0FZ0/btszrLtwxrX/Uo9vWbEdUiWFz59dYk7dWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P0FZ0/btszrLtwxrX/Uo9vWbEdUiWFz59dYk7dWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP0FZ0%2FbtszrLtwxrX%2FUo9vWbEdUiWFz59dYk7dWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Controls에 추가합니다.&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698572880044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;folder2.add(exporter1, 'ExportglTFFileName');
folder2.add(exporter1, 'ExportglTFFilebinary');
folder2.add(exporter1, 'ExportglTFFunction').name('Export');&lt;/code&gt;&lt;/pre&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;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkLHki/btszkR23wjK/VjlLiKdJBNNtMuQy3ToWa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkLHki/btszkR23wjK/VjlLiKdJBNNtMuQy3ToWa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkLHki/btszkR23wjK/VjlLiKdJBNNtMuQy3ToWa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkLHki%2FbtszkR23wjK%2FVjlLiKdJBNNtMuQy3ToWa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&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;그리고 Controls에 ExportglTFFileName과 ExportglTFFilebinary가 추가됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uabee/btszqeJqIx9/ey8eGT0ZfYGodXDraEasJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uabee/btszqeJqIx9/ey8eGT0ZfYGodXDraEasJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uabee/btszqeJqIx9/ey8eGT0ZfYGodXDraEasJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuabee%2FbtszqeJqIx9%2Fey8eGT0ZfYGodXDraEasJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2556&quot; height=&quot;1644&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFilebinary가 체크되면 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFileName의 확장자를 &quot;.glb&quot;로 변경시키고 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFilebinary가 체크되지 않으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFileName의 확장자를 &quot;.gltf&quot;로 변경되게 함수를 만듭니다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698573987239&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 파일 저장 형식에 따른 파일 확장자 변경
function updatesglTFFileName() {
    let fileName = exporter1.ExportglTFFileName;
    const pos = fileName.lastIndexOf('.');
    if (pos &amp;gt; 0) {
        if (exporter1.ExportglTFFilebinary) {
            exporter1.ExportglTFFileName = fileName.substring(0, pos) + &quot;.glb&quot;;
        } else {
            exporter1.ExportglTFFileName = fileName.substring(0, pos) + &quot;.gltf&quot;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9B3Qu/btszk24ntwL/f2xRmcPfeEc3g8scexGRKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9B3Qu/btszk24ntwL/f2xRmcPfeEc3g8scexGRKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9B3Qu/btszk24ntwL/f2xRmcPfeEc3g8scexGRKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9B3Qu%2Fbtszk24ntwL%2Ff2xRmcPfeEc3g8scexGRKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;확장자를 변경하는 함수를 ExportglTFFilebinary에서 onChange()로 처리되게 추가하고 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Controls에 있는 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFileName&lt;/span&gt;이&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;변경된 확장로 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;변경되도록 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;listen() 함수를 추가합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698573816208&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;folder2.add(exporter1, 'ExportglTFFileName').listen();
folder2.add(exporter1, 'ExportglTFFilebinary').onChange(updatesglTFFileName);
folder2.add(exporter1, 'ExportglTFFunction').name('Export');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vUQiy/btszjDYDkF6/SAPaqZKaDeqGGh9Yz1qzZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vUQiy/btszjDYDkF6/SAPaqZKaDeqGGh9Yz1qzZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vUQiy/btszjDYDkF6/SAPaqZKaDeqGGh9Yz1qzZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvUQiy%2FbtszjDYDkF6%2FSAPaqZKaDeqGGh9Yz1qzZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1660&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Controls&lt;/span&gt;에서&amp;nbsp;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFilebinary를 선택할 때마다 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;ExportglTFFileName의 확장자가 변경되는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nnCwe/btszjz2WHkf/TiAMiYcR0evwR0mNBrPjw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nnCwe/btszjz2WHkf/TiAMiYcR0evwR0mNBrPjw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nnCwe/btszjz2WHkf/TiAMiYcR0evwR0mNBrPjw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnnCwe%2Fbtszjz2WHkf%2FTiAMiYcR0evwR0mNBrPjw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2556&quot; height=&quot;1644&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1644&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;그리고 Controls에서 &quot;Export&quot; 버튼을 클릭하면 변경된 확장자로 다운로드되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cutaR0/btszkbngh4A/WHvn0Om3yX8rVqir0PvAn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cutaR0/btszkbngh4A/WHvn0Om3yX8rVqir0PvAn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cutaR0/btszkbngh4A/WHvn0Om3yX8rVqir0PvAn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcutaR0%2Fbtszkbngh4A%2FWHvn0Om3yX8rVqir0PvAn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2556&quot; height=&quot;1644&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>BLOB</category>
      <category>GLTFExporter</category>
      <category>Three.js</category>
      <category>URL.createObjectURL</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/304</guid>
      <comments>https://carrotweb.tistory.com/304#entry304comment</comments>
      <pubDate>Sun, 29 Oct 2023 19:44:33 +0900</pubDate>
    </item>
    <item>
      <title>three.js 3D 모델 추출하기(glTF JSON or Binary) - GLTFExporter</title>
      <link>https://carrotweb.tistory.com/303</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 우리가 만든 Object(객체, 3D 모델)을 glTF 포맷으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;추출&lt;/span&gt;해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. import를 통해 GLTFExporter(GLTF익스포트) 모듈을 가져옵니다. GLTFExporter(GLTF&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;익스포트)&lt;/span&gt;는 Scence(씬, 장면)이나 Scence(씬, 장면)에 있는 Object(객체)들을 glTF 포맷으로 추출합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696833302687&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN7lha/btsxqsYL3Xa/qgi4YpIoq5KY9LzUgwfyT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN7lha/btsxqsYL3Xa/qgi4YpIoq5KY9LzUgwfyT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN7lha/btsxqsYL3Xa/qgi4YpIoq5KY9LzUgwfyT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN7lha%2FbtsxqsYL3Xa%2Fqgi4YpIoq5KY9LzUgwfyT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&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;GLTFExporter(GLTF익스포트) 모듈의 실제 위치는 &quot;\node_modules\three\examples\jsm\exporters\GLTFExporter.js&quot; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ECQyb/btsxsRQ3r9i/6wqnlppOPyfkGCRBE31zL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ECQyb/btsxsRQ3r9i/6wqnlppOPyfkGCRBE31zL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ECQyb/btsxsRQ3r9i/6wqnlppOPyfkGCRBE31zL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FECQyb%2FbtsxsRQ3r9i%2F6wqnlppOPyfkGCRBE31zL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Scence(씬, 장면)이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Scence(씬, 장면)에 있는 Object(객체)들을 glTF 포맷으로 추출하기 위한 함수를 만듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696861154364&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExportglTF() {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbJIxE/btsxkESQWQK/pqA5qsONWCz5Zn5BGebQ70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbJIxE/btsxkESQWQK/pqA5qsONWCz5Zn5BGebQ70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbJIxE/btsxkESQWQK/pqA5qsONWCz5Zn5BGebQ70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbJIxE%2FbtsxkESQWQK%2FpqA5qsONWCz5Zn5BGebQ70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&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;3. glTF 포맷으로 추출하기 위해 함수에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;GLTFExporter(GLTF익스포트)&lt;/span&gt;를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696834593586&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GLTFExporter()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터는&amp;nbsp;없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLTFExporter(GLTF익스포트)를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696834708809&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExportglTF() {
    // GLTF익스포트 생성
    const exporter = new GLTFExporter();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsa6K6/btsxH1SLMb0/FiKkrKwzdEkG0OsN6ReFJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsa6K6/btsxH1SLMb0/FiKkrKwzdEkG0OsN6ReFJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsa6K6/btsxH1SLMb0/FiKkrKwzdEkG0OsN6ReFJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsa6K6%2FbtsxH1SLMb0%2FFiKkrKwzdEkG0OsN6ReFJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Scence(씬, 장면)이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Scence(씬, 장면)에 있는 Object(객체)들을 GLTFExporter(GLTF익스포트)의 parse(파서) 함수로 추출합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696834939482&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.parse (input : Object3D, onCompleted : Function, onError : Function, options : Object)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;input: 추출할 Scence(씬, 장면) 또는 Scence(씬, 장면)에 있는 Object(객체)입니다.&lt;/li&gt;
&lt;li&gt;onCompleted: glTF 포맷으로 추출된 후 호출되는 함수입니다. (함수 파라미터로 &amp;nbsp;glTF JSON 또는 바이너리(ArrayBuffer)를 넘겨줍니다.)&lt;/li&gt;
&lt;li&gt;onError: glTF 생성 중에 오류가 발생하면 호출되는 함수입니다. (함수 파라미터로 에러 정보를 넘겨줍니다.)&lt;/li&gt;
&lt;li&gt;options:&amp;nbsp;추출할&amp;nbsp;때&amp;nbsp;사용되는&amp;nbsp;옵션입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input은 다음과 같이 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Scence(씬, 장면)를 추출할 경우&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1696836255982&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 하나의 Scence(씬, 장면)를 추출할 경우
exporter.parse(scene1, ...)
// 여러 개의 Scence(씬, 장면)를 추출할 경우 (배열로 추가)
exporter.parse([scene1, scene2], ...)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scence(씬, 장면)에 있는 Object(객체)를 추출할 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1696836444820&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 하나의 객체(3D 모델)를 추출할 경우
exporter.parse(object1, ...)
// 여러 개의 객체(3D 모델)를 추출할 경우 (배열로 추가)
exporter.parse([object1, object2], ...)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scence(씬, 장면)과 Object(객체)를 썩어서 추출할 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1696836630538&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 하나 또는 여러개의 Scence(씬, 장면)과 객체(3D 모델)를 썩어서 추출할 경우
exporter.parse([scene1, object1, object2, scene2], ...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object(객체)나 Scence(씬, 장면)과 객체를 썩어서 추출할 경우에는 새로운 Scence(씬, 장면)을&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt; 생성하여 glTF 포맷으로 추출됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options은 다음과 같이 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;trs: 추출되는 로드마다 Matrix 정보 대신 Position(위치), Rotation(회전), Scale(크기) 정보로 내보낼지 여부입니다. 기본값 false&lt;/li&gt;
&lt;li&gt;onlyVisible:&amp;nbsp;보이는&amp;nbsp;Object(객체)만&amp;nbsp;추출할지&amp;nbsp;여부입니다.&amp;nbsp;기본값&amp;nbsp;true&lt;/li&gt;
&lt;li&gt;binary:&amp;nbsp;바이너리(.glb)&amp;nbsp;형식으로&amp;nbsp;추출할지&amp;nbsp;여부입니다.&amp;nbsp;기본값&amp;nbsp;false&lt;/li&gt;
&lt;li&gt;maxTextureSize: 텍스처(이미지)의 최대 크기(너비와 높이를 곱한 값)를 지정된 값으로 제한합니다. 기본값 Infinity(무한대)&lt;/li&gt;
&lt;li&gt;animations:&amp;nbsp;추출할&amp;nbsp;애니메이션&amp;nbsp;목록입니다.&lt;/li&gt;
&lt;li&gt;includeCustomExtensions: userData.gltfExtensions 속성으로 정의된 사용자 정보를 내보낼지 여부입니다. 기본값 false&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Object(객체, 3D 모델)을&amp;nbsp;&lt;/span&gt;바이너리(.glb) 형식으로 추출하기 위해서 options에 binary를 true로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696859109064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExportglTF() {
    // GLTF익스포트 생성
    const exporter = new GLTFExporter();

    // GLTF익스포트 옵션 - 바이너리 형식으로 추출
    const options = {
        binary: true
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkiIpe/btsxqtDlKXs/HXYsx3t91Sn2Kd34qws02k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkiIpe/btsxqtDlKXs/HXYsx3t91Sn2Kd34qws02k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkiIpe/btsxqtDlKXs/HXYsx3t91Sn2Kd34qws02k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkiIpe%2FbtsxqtDlKXs%2FHXYsx3t91Sn2Kd34qws02k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;parse 함수로 주사위 객체를 glTF 포맷으로 추출하게 input에 box2를 설정하고 onCompleted에서는 생성된 glTF 포맷 정보를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;콘솔 로그로&lt;span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;출력되게 함수를 설정하고&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;onError에서는 오류 정보가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;콘솔 로그로&amp;nbsp;&lt;/span&gt;출력되게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696861605556&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExportglTF() {
    // GLTF익스포트 생성
    const exporter = new GLTFExporter();

    // GLTF익스포트 옵션 - 바이너리 형식으로 추출
    const options = {
        binary: true
    };

    exporter.parse(
        box2,
        function (gltf) {
            if (gltf instanceof ArrayBuffer) {
                console.log(&quot;export gltf:&quot;, gltf);
            } else {
                console.log(&quot;export gltf:&quot;, gltf);
            }
        },
        function (error) {
            console.error(&quot;exporter error:&quot;, error);
        },
        options
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q6ou4/btsxkB9EDeI/rx0foEKOVpkOTdkizxR9Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q6ou4/btsxkB9EDeI/rx0foEKOVpkOTdkizxR9Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q6ou4/btsxkB9EDeI/rx0foEKOVpkOTdkizxR9Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq6ou4%2FbtsxkB9EDeI%2Frx0foEKOVpkOTdkizxR9Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&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;5. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF 추출 함수를 호출하기 위해서 Controls&lt;/span&gt;에 별도의 Folder(폴더)를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696862170654&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러에 glTF 포맷으로 추출하기 위한 Folder(폴더) 추가
const folder2 = gui.addFolder('GLTFExporter');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clmGaw/btsxkEL4qpy/FUSAXxl9sD6ut2Q2DktAo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clmGaw/btsxkEL4qpy/FUSAXxl9sD6ut2Q2DktAo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clmGaw/btsxkEL4qpy/FUSAXxl9sD6ut2Q2DktAo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclmGaw%2FbtsxkEL4qpy%2FFUSAXxl9sD6ut2Q2DktAo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&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;Controls에서 버튼을 생성하기 위해서는 객체의 parameters(파라미터)를 값을 function으로 선언하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696863059621&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const myObject = {
	myFunction: function() { ... }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 위에서 만든 ExportglTF() 함수로 객체를 생성하고 Folder(폴더)에 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696862411349&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const exporter1 = {
    ExportglTFFunction : ExportglTF
};

folder2.add(exporter1, 'ExportglTFFunction').name('Export');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m4GND/btsxsoOSDOf/uiH4hkfKQtolqUaN4lUba0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m4GND/btsxsoOSDOf/uiH4hkfKQtolqUaN4lUba0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m4GND/btsxsoOSDOf/uiH4hkfKQtolqUaN4lUba0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm4GND%2FbtsxsoOSDOf%2FuiH4hkfKQtolqUaN4lUba0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1656&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1656&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;2580&quot; data-origin-height=&quot;1646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7xmyq/btsxrjNST2I/KmNYSukUANYxvaoCULyRyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7xmyq/btsxrjNST2I/KmNYSukUANYxvaoCULyRyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7xmyq/btsxrjNST2I/KmNYSukUANYxvaoCULyRyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7xmyq%2FbtsxrjNST2I%2FKmNYSukUANYxvaoCULyRyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2580&quot; height=&quot;1646&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Controls에서 &quot;Export&quot; 버튼을 클릭하면 콘솔에 바이너리(ArrayBuffer)로 추출된 glTF 정보가 출력됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L1X9N/btsxsmcu49r/BRUf6g7TNyavmuVA3TRJQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L1X9N/btsxsmcu49r/BRUf6g7TNyavmuVA3TRJQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L1X9N/btsxsmcu49r/BRUf6g7TNyavmuVA3TRJQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL1X9N%2Fbtsxsmcu49r%2FBRUf6g7TNyavmuVA3TRJQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2580&quot; height=&quot;1646&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1646&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;만약 JSON 형식으로 추출하고 싶다면 options에서 binary를 false로 설정하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696864607388&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GLTF익스포트 옵션 - JSON 형식으로 추출
const options = {
    binary: 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;size16&quot;&gt;그리면 콘솔에 JSON 형식으로 추출된 glTF 정보가 출력됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCAxb/btsxUauWsQ3/wbYhCNQTgKa3NMu99nAEgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCAxb/btsxUauWsQ3/wbYhCNQTgKa3NMu99nAEgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCAxb/btsxUauWsQ3/wbYhCNQTgKa3NMu99nAEgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCAxb%2FbtsxUauWsQ3%2FwbYhCNQTgKa3NMu99nAEgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2580&quot; height=&quot;1646&quot; data-origin-width=&quot;2580&quot; data-origin-height=&quot;1646&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;이어서 추출된 glTF 정보를 .gltf 파일과 .glb 파일로 다운로드해 보겠습니다.&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>glTF</category>
      <category>GLTFExporter</category>
      <category>parse</category>
      <category>Three.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/303</guid>
      <comments>https://carrotweb.tistory.com/303#entry303comment</comments>
      <pubDate>Tue, 10 Oct 2023 00:10:02 +0900</pubDate>
    </item>
    <item>
      <title>three.js 3D 모델 불러오기(glTF 파일 로드) - GLTFLoader</title>
      <link>https://carrotweb.tistory.com/302</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 외부에서 만들어진 3D 모델을 로드하여 Scence(씬, 장면)에 추가해 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 모델들은 다양한 파일 포맷으로 저장됩니다.&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;대표적인 3D 파일 포맷으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;OBJ, FBX, glTF, USD가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 파일 포맷 중에서 three.js에서는 glTF 파일 포맷을 사용하는 것을 추천하고 있습니다.&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;1766&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csDZSm/btsnZJ12OLE/FkDogOPDrFfjguu3mkQFDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csDZSm/btsnZJ12OLE/FkDogOPDrFfjguu3mkQFDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csDZSm/btsnZJ12OLE/FkDogOPDrFfjguu3mkQFDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsDZSm%2FbtsnZJ12OLE%2FFkDogOPDrFfjguu3mkQFDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1766&quot; height=&quot;1032&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF(GL Transmission Format, GL 트랜스미션 포맷)는 응용프로그램에서 3D 장면과 3D 모델을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;효율적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전송하고 로드시키는 방법에 대한 사양(규격, 명세)으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;로열티가 없는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;오픈 소스 파일 포맷입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;glTF는 3D Assets(3D 에셋, 3D 자산(모델 정보, 텍스처, 바이너리 데이터))의 파일 크기를 최소화하였습니다. 그리고 3D Assets(3D 에셋)을 사용하기 위해 압축을 풀고 처리하는 런타임 프로세싱을 최소화하였습니다. (이것은 .glb 파일에 적용되는 내용입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;glTF는 제작 작업 절차를 간소화하고 산업 전반에서 콘텐츠가 서로 호환되어 사용(상호운용)이 가능하게 3D 콘텐츠 도구 및 서비스를 위한 확장 가능한 공통 배포 포맷을 정의하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 설명은 glTF(&lt;a href=&quot;https://github.com/KhronosGroup/glTF&quot;&gt;https://github.com/KhronosGroup/glTF&lt;/a&gt;)를 참조하시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 glTF는 어떤 구조로 되어 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;glTF&amp;nbsp;2.0&amp;nbsp;Specification(&lt;a href=&quot;https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html&lt;/a&gt;)를 보면 다음과 같이 glTF의 Asset(에셋)에 대해 설명하고 있습니다.&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;1784&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d86YPz/btsnYZkjNiY/gCJfZTV2hawIh7IBfNu0s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d86YPz/btsnYZkjNiY/gCJfZTV2hawIh7IBfNu0s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d86YPz/btsnYZkjNiY/gCJfZTV2hawIh7IBfNu0s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd86YPz%2FbtsnYZkjNiY%2FgCJfZTV2hawIh7IBfNu0s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1784&quot; height=&quot;534&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;534&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;glTF Asset(에셋, 자산)은 다음과 같이 표현됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scence(씬, 장면)에 대한 전체 설명이 담긴&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;JSON 포맷 파일 (.gltf) : 노드 계층 구조, 머티리얼, 카메라 정보뿐만 아니라 메시, 애니메이션 그리고 기타 구성에 대한 설명자 정보도 포함됩니다.&lt;/li&gt;
&lt;li&gt;지오메트리, 애니메이션 그리고 기타 버퍼 기반 데이터가 담긴 바이너리 파일 (.bin)&lt;/li&gt;
&lt;li&gt;텍스처 이미지가 담긴 이미지 파일 (.jpg, .png, 등등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;즉, 아래 샘플(&lt;a href=&quot;https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoomBox/glTF&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoomBox/glTF&lt;/a&gt;)처럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Asset(에셋,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;자산)은&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;3개의 유형으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;JSON 포맷 파일 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;(.gltf)&lt;/span&gt;, 데이터 파일 (.bin), 이미지 파일 (.jpg, .png, 등등)로 구성됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2546&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diYj7G/btson1HWn1a/lDCRFKOXAJ0FC08vmSm420/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diYj7G/btson1HWn1a/lDCRFKOXAJ0FC08vmSm420/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diYj7G/btson1HWn1a/lDCRFKOXAJ0FC08vmSm420/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiYj7G%2Fbtson1HWn1a%2FlDCRFKOXAJ0FC08vmSm420%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2546&quot; height=&quot;898&quot; data-origin-width=&quot;2546&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;3D 모델에 대한 정보는&lt;span&gt;&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JSON 포맷으로 .gltf 파일로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;저장되고 3D 모델의 지오메트리, 애니메이션, 스킨 정보들은 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;.bin 파일로 저장되고 3D 모델&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;의&lt;span&gt; 머티리얼에서 사용되는 이미지들은 각각의 이미지 파일(.jpg, .png, 등등)로 저장됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고 JSON 포맷 파일(.gltf)에는 &lt;/span&gt;데이터가 담긴&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;바이너리 파일 (.bin)과 이미지 파일 (.jpg, .png)들이 연결되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 JSON 포맷 파일, 데이터 파일, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모든 이미지 파일&lt;/span&gt;들이 하나의 바이너리 파일 (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;.glb&lt;/span&gt;)로 결합되어 저장됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2552&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duzPbT/btsorkPuVED/kduXsr2hbyHga22YXIBF0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duzPbT/btsorkPuVED/kduXsr2hbyHga22YXIBF0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duzPbT/btsorkPuVED/kduXsr2hbyHga22YXIBF0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduzPbT%2FbtsorkPuVED%2FkduXsr2hbyHga22YXIBF0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2552&quot; height=&quot;504&quot; data-origin-width=&quot;2552&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;대부분 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;glTF는&lt;/span&gt; .glb 파일로 저장되고 배포됩니다. 그 이유는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터 파일 (.bin)과&amp;nbsp;&lt;/span&gt;이미지 파일 (.jpg, .png, 등등)를 함께 배포하는 것보다 배포가 용이하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 glTF 파일을 로드해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. import를 통해 GLTFLoader(GLTF로더) 모듈을 가져옵니다. GLTFLoader(GLTF로더)는 glTF 파일을 로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694326745407&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LWoQ1/btstxlomfvh/1g7tNpkxdYSMVKwztuWxM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LWoQ1/btstxlomfvh/1g7tNpkxdYSMVKwztuWxM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LWoQ1/btstxlomfvh/1g7tNpkxdYSMVKwztuWxM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLWoQ1%2Fbtstxlomfvh%2F1g7tNpkxdYSMVKwztuWxM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&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;GLTFLoader(GLTF로더) 모듈의 실제 위치는 &quot;\node_modules\three\examples\jsm\loaders\GLTFLoader.js&quot; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvUIBD/btstywJD7Xm/nKP127GTkXXLtFqTG7QGF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvUIBD/btstywJD7Xm/nKP127GTkXXLtFqTG7QGF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvUIBD/btstywJD7Xm/nKP127GTkXXLtFqTG7QGF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvUIBD%2FbtstywJD7Xm%2FnKP127GTkXXLtFqTG7QGF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. GLTF로더로 로드할 모델을 glTF-Sample-Models(&lt;a href=&quot;https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/BoomBox/glTF-Binary/BoomBox.glb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/BoomBox/glTF-Binary/BoomBox.glb&lt;/a&gt;)에서 다운로드합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc0tJd/btstwZTnjBO/nCwtVaxMNZj1qMD5pio1DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc0tJd/btstwZTnjBO/nCwtVaxMNZj1qMD5pio1DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc0tJd/btstwZTnjBO/nCwtVaxMNZj1qMD5pio1DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc0tJd%2FbtstwZTnjBO%2FnCwtVaxMNZj1qMD5pio1DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1598&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&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;그리고 프로젝트에 resources \ textures 폴더 다운로드한 BoomBox 파일(BoomBox.glb)을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbGmhG/btsts5Nmooh/JfLBxr2cvCt41UWgnLiPS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbGmhG/btsts5Nmooh/JfLBxr2cvCt41UWgnLiPS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbGmhG/btsts5Nmooh/JfLBxr2cvCt41UWgnLiPS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbGmhG%2Fbtsts5Nmooh%2FJfLBxr2cvCt41UWgnLiPS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VSCode에서는 glTF 파일을 볼 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. GLTFLoader(GLTF로더)를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694338137882&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GLTFLoader(manager : LoadingManager)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;manager(매니저) : GLTF로더가 사용할 LoadingManager(로딩 매니저)입니다. 기본값 THREE.DefaultLoadingManager&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본 로딩 매니저로 GLTFLoader(GLTF로더)를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694338159737&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GLTF로더 생성
const loader = new GLTFLoader();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzQZws/btstxlomhmN/soCdrBoAul12vfvIZxGe9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzQZws/btstxlomhmN/soCdrBoAul12vfvIZxGe9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzQZws/btstxlomhmN/soCdrBoAul12vfvIZxGe9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzQZws%2FbtstxlomhmN%2FsoCdrBoAul12vfvIZxGe9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. resources \ textures 폴더에 있는 BoomBox 파일(BoomBox.glb)을 GLTFLoader(GLTF로더)로 로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694331908174&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.load (url : String, onLoad : Function, onProgress : Function, onError : Function)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;url: .glb 파일 또는 .gltf 파일의 웹 주소입니다.&lt;/li&gt;
&lt;li&gt;onLoad: 로딩이 성공적으로 완료된 후 호출되는 함수입니다. (함수 파라메타로&amp;nbsp;&amp;nbsp;glTF JSON를 넘겨줍니다.)&lt;/li&gt;
&lt;li&gt;onProgress: 로딩이 진행되는 동안 호출될 함수입니다. (함수 파라메타로 로드 정보를 넘겨줍니다. - 옵션)&lt;/li&gt;
&lt;li&gt;onError: 로딩 중 오류가 발생하면 호출되는 함수입니다. ( 함수 파라메타로 에러 정보를 넘겨줍니다. - 옵션)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onLoad 함수에서는 콘솔 로그로 로드한 glTF의 JSON 정보를 출력하고 onProgress 함수에서는 콘솔 로그로 로딩 프로세스를 출력하고 onError 함수에서는 콘솔 로그로 오류 정보를 출력하게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694330991939&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// glTF 파일 로드
loader.load('resources/textures/BoomBox.glb', 
    function (gltf) {
        console.log(gltf);
    },
    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
	}, 
    function (error) {
        console.error(error);
    })
;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oonzN/btstG8H0EaD/X3iCEfPYu25svHpqNLuf00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oonzN/btstG8H0EaD/X3iCEfPYu25svHpqNLuf00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oonzN/btstG8H0EaD/X3iCEfPYu25svHpqNLuf00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoonzN%2FbtstG8H0EaD%2FX3iCEfPYu25svHpqNLuf00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;콘솔 로그에 로딩 프로세스가 출력되고 glTF의 JSON 정보가 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxFAa/btstCXGHq5M/DKEy6oQjAE2rWDcDUJNRsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxFAa/btstCXGHq5M/DKEy6oQjAE2rWDcDUJNRsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxFAa/btstCXGHq5M/DKEy6oQjAE2rWDcDUJNRsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxFAa%2FbtstCXGHq5M%2FDKEy6oQjAE2rWDcDUJNRsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1598&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&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;5. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Scence(씬, 장면)에&lt;span&gt; 로드한 모델을 추가하기 위해 onLoad 함수에 glt의 scene를 추가하고 로드한 모델을 보기 위해 Camera(카메라)의 Focus Point(포커스 포인트, 초점)과 Position(포지션, 위치)를 변경합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694333038619&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// glTF 파일 로드
loader.load('resources/textures/BoomBox.glb', 
    function (gltf) {
        console.log(gltf);
        scene.add(gltf.scene);
        // 육면체 옆으로 위치 변경
        gltf.scene.position.set(-2, 0, 0);
        // 카메라 초점 변경
        controls.target.copy(gltf.scene.position);
        // 카메라 위치 변경
        camera.position.set(-2, 1, 2);
    },
    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
	}, 
    function (error) {
        console.error(error);
    })
;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ewJS0Z/btstxpqLBFk/DFW5TmYAVtvXhWNkYoBf2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ewJS0Z/btstxpqLBFk/DFW5TmYAVtvXhWNkYoBf2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ewJS0Z/btstxpqLBFk/DFW5TmYAVtvXhWNkYoBf2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FewJS0Z%2FbtstxpqLBFk%2FDFW5TmYAVtvXhWNkYoBf2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&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;2532&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHFqd/btstCH41LLj/dsXWoqisrCnTH6eAMAqd2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHFqd/btstCH41LLj/dsXWoqisrCnTH6eAMAqd2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHFqd/btstCH41LLj/dsXWoqisrCnTH6eAMAqd2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHFqd%2FbtstCH41LLj%2FdsXWoqisrCnTH6eAMAqd2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1598&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;로드된&lt;span&gt;&amp;nbsp;&lt;/span&gt;모델의 크기가 어떻게 되는지 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;onLoad 함수에서 로드된 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모델의 객체를 가져옵니다. 그리고 객체의 크기를 가져와서 콘솔 로그로 출력합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694335481823&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 로드된 모델의 크기 확인
var gltfObject = new THREE.Box3().setFromObject(gltf.scene);
var gltfObjectSize = gltfObject.getSize(new THREE.Vector3());
console.log(gltfObjectSize);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl4yXd/btstG7h19l5/cu9loPrtlMvOIPMmLwsvkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl4yXd/btstG7h19l5/cu9loPrtlMvOIPMmLwsvkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl4yXd/btstG7h19l5/cu9loPrtlMvOIPMmLwsvkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl4yXd%2FbtstG7h19l5%2Fcu9loPrtlMvOIPMmLwsvkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&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;2532&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TR8HV/btstwWPR8hz/OGDTzSTZgKVMQAD2S5CCY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TR8HV/btstwWPR8hz/OGDTzSTZgKVMQAD2S5CCY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TR8HV/btstwWPR8hz/OGDTzSTZgKVMQAD2S5CCY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTR8HV%2FbtstwWPR8hz%2FOGDTzSTZgKVMQAD2S5CCY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1598&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔 로그에 보면 크기가 매우 작은 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694335903591&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;x&quot;: 0.019842308,
    &quot;y&quot;: 0.01954326,
    &quot;z&quot;: 0.0201524906
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델의 크기를 키우기 위해 전체 Scale(스케일)의 크기를 변경합니다. 크기를 100배 정도 크게 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1694335779445&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// glTF 파일 로드
loader.load('resources/textures/BoomBox.glb', 
    function (gltf) {
        console.log(gltf);

        // 로드된 모델의 크기 확인
        var gltfObject = new THREE.Box3().setFromObject(gltf.scene);
        var gltfObjectSize = gltfObject.getSize(new THREE.Vector3());
        console.log(gltfObjectSize);

        // 모델의 스케일 변경
        gltf.scene.scale.x = 100;
        gltf.scene.scale.y = 100;
        gltf.scene.scale.z = 100;

        scene.add(gltf.scene);
        // 육면체 옆으로 위치 변경
        gltf.scene.position.set(-2, 0, 0);
        // 카메라 초점 변경
        controls.target.copy(gltf.scene.position);
        // 카메라 위치 변경
        camera.position.set(-2, 1, 2);
    },
    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
	}, 
    function (error) {
        console.error(error);
    })
;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgTnIC/btstvPwYRxi/bsV5VTQZjfAgoGTjTQjVq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgTnIC/btstvPwYRxi/bsV5VTQZjfAgoGTjTQjVq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgTnIC/btstvPwYRxi/bsV5VTQZjfAgoGTjTQjVq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgTnIC%2FbtstvPwYRxi%2FbsV5VTQZjfAgoGTjTQjVq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1664&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1664&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;2532&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUU6bR/btstsM8oHhl/kGxFtNrpNKI4Vp80TH5wx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUU6bR/btstsM8oHhl/kGxFtNrpNKI4Vp80TH5wx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUU6bR/btstsM8oHhl/kGxFtNrpNKI4Vp80TH5wx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUU6bR%2FbtstsM8oHhl%2FkGxFtNrpNKI4Vp80TH5wx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1598&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1598&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;모델의 Scale(스케일)을 크게 하니 적당한 크기로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>glb</category>
      <category>glTF</category>
      <category>GLTFLoader</category>
      <category>scale</category>
      <category>setFromObject</category>
      <category>Three.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/302</guid>
      <comments>https://carrotweb.tistory.com/302#entry302comment</comments>
      <pubDate>Sun, 10 Sep 2023 18:35:01 +0900</pubDate>
    </item>
    <item>
      <title>three.js GUI Controls - lil-gui, addFolder, geometry.dispose</title>
      <link>https://carrotweb.tistory.com/301</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 만든 Controls에 아래의 three.js 사이트의 doc 처럼 SphereGeometry(스피어 지오메트리, 구 기하 도형)에 대한 속성들을 추가해 보겠습니다. (&lt;a href=&quot;https://threejs.org/docs/index.html#api/en/geometries/SphereGeometry&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://threejs.org/docs/index.html#api/en/geometries/SphereGeometry&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img1.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buiAqr/btsrH6Tb71k/MIdHO47TlcRuIW9Y98tyEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buiAqr/btsrH6Tb71k/MIdHO47TlcRuIW9Y98tyEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buiAqr/btsrH6Tb71k/MIdHO47TlcRuIW9Y98tyEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuiAqr%2FbtsrH6Tb71k%2FMIdHO47TlcRuIW9Y98tyEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;1054&quot; data-filename=&quot;img1.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1054&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;우선 Camera(카메라)의 위치(position)를 구 객체(sphere1) 앞으로 이동시키겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 OrbitControls(오빗 컨트롤, 궤도 컨트롤)를 사용하고 있습니다. 그래서 항상 Camera(카메라)의 Focus Point(포커스 포인트, 초점)가 {x: 0, y: 0, z: 0} 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692540237136&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(controls.target);
--&amp;gt; Vector3&amp;nbsp;{x: 0, y: 0, z: 0}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(OrbitControls(오빗 컨트롤, 궤도 컨트롤)를 사용하면 Camera(카메라)의 lookAt 함수를 사용해도 적용되지 않습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Camera(카메라)의&lt;span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Focus Point(포커스 포인트,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;초점)을 구 객체(sphere1)의 위치로 조정하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692540440096&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(sphere1.position);
--&amp;gt; Vector3&amp;nbsp;{x: 2, y: 1, z: -2}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrbitControls(오빗 컨트롤, 궤도 컨트롤)의 target에 구 객체(sphere1)의 위치 값을 복사하거나 Vector3(백터)로 위치 값을 생성하여 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692540497481&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카메라 초점 변경
controls.target.copy(sphere1.position);

또는

// 카메라 초점 변경
controls.target = new THREE.Vector3(2, 1, -2);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBUY7K/btssgsoIOhQ/hrHKUiZoMQHJY607fxMvyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBUY7K/btssgsoIOhQ/hrHKUiZoMQHJY607fxMvyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBUY7K/btssgsoIOhQ/hrHKUiZoMQHJY607fxMvyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBUY7K%2FbtssgsoIOhQ%2FhrHKUiZoMQHJY607fxMvyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&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;그러면 Camera(카메라)의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Focus Point(포커스 포인트,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;초점)가 구 객체(sphere1)의 위치로 변경됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czpiQ6/btsrBMPtZUS/tOqkSxg1VK3kqYAfsRWdlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czpiQ6/btsrBMPtZUS/tOqkSxg1VK3kqYAfsRWdlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czpiQ6/btsrBMPtZUS/tOqkSxg1VK3kqYAfsRWdlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczpiQ6%2FbtsrBMPtZUS%2FtOqkSxg1VK3kqYAfsRWdlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2498&quot; height=&quot;1606&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약, OrbitControls(오빗 컨트롤, 궤도 컨트롤)의 target에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;구 객체(sphere1)의 위치 값을 바로 사용하면 OrbitControls(오빗 컨트롤, 궤도 컨트롤)에서 오른쪽 마우스로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Camera(카메라)의&lt;span&gt; &lt;/span&gt;&lt;/span&gt;위치를 변경하면 구 객체(sphere1)가 항상 중앙에 위치하게 되어 라인 세그먼트즈&amp;nbsp; 객체와 분리되는 문제가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692541044687&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;controls.target = sphere1.position;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4SmYz/btsrB2xL3dX/0pzs0eUjBnGPmwIqjm72s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4SmYz/btsrB2xL3dX/0pzs0eUjBnGPmwIqjm72s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4SmYz/btsrB2xL3dX/0pzs0eUjBnGPmwIqjm72s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4SmYz%2FbtsrB2xL3dX%2F0pzs0eUjBnGPmwIqjm72s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2498&quot; height=&quot;1606&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1606&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;화면에 구 객체(sphere1)가 크게 보이도록 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Camera(카메라)의 위치(position)를&lt;span&gt; 조정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692541947497&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카메라 위치 변경
camera.position.set(2, 2.5, -1);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btPYjJ/btsshtHuYB3/8XKZTlmpCG932fiQI7wLDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btPYjJ/btsshtHuYB3/8XKZTlmpCG932fiQI7wLDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btPYjJ/btsshtHuYB3/8XKZTlmpCG932fiQI7wLDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtPYjJ%2FbtsshtHuYB3%2F8XKZTlmpCG932fiQI7wLDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&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;2498&quot; data-origin-height=&quot;1606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc2hft/btsryswXpAT/eS1lc5PNsiJzSEMXn8Vz8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc2hft/btsryswXpAT/eS1lc5PNsiJzSEMXn8Vz8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc2hft/btsryswXpAT/eS1lc5PNsiJzSEMXn8Vz8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc2hft%2FbtsryswXpAT%2FeS1lc5PNsiJzSEMXn8Vz8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2498&quot; height=&quot;1606&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1606&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;구 객체(sphere1)와 라인 세그먼트즈 객체(sphere1WireframeLine)는 구 그룹(sphere1Group)으로 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구 그룹(sphere1Group)의 첫 번째 자식은 구 객체(sphere1)이고 두 번째 자식은 와이어프레임을 선으로 그린 세그먼트즈 객체(sphere1WireframeLine)입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692542161539&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(sphere1Group.children);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boI6ZQ/btsrARKiXHy/QEl2QMI0e9kvOBWvCUbXhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boI6ZQ/btsrARKiXHy/QEl2QMI0e9kvOBWvCUbXhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boI6ZQ/btsrARKiXHy/QEl2QMI0e9kvOBWvCUbXhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboI6ZQ%2FbtsrARKiXHy%2FQEl2QMI0e9kvOBWvCUbXhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2498&quot; height=&quot;1608&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;1608&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;라인 세그먼트즈 객체(sphere1WireframeLine)는 와이어프레임 지오메트리(sphere1WireframeGeometry)를 parameters(파라미터)로 전달받아 선으로 그립니다. 그리고&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;와이어프레임&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;지오메트리(sphere1WireframeGeometry)는 parameters(파라미터)로 구 지오메트리(sphere1Geometry)를 전달받아 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;와이어프레임을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 말한 것처럼 parameters(파라미터) 값으로 전달받기 때문에 값을 변경하여도 생성된 객체는 변경되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692544965823&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(sphere1Group.children[1].geometry.parameters.geometry.parameters.heightSegments);
--&amp;gt; 32

sphere1Group.children[1].geometry.parameters.geometry.parameters.heightSegments = 4;

console.log(sphere1Group.children[1].geometry.parameters.geometry.parameters.heightSegments);
--&amp;gt; 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 객체를 생성할 때 사용된 정보들은 객체 생성 후 변경되어도 다시 Rendering(렌더링)되지 않습니다.&lt;/p&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;그래서 Scence(씬, 장면)에 객체를 제거하고 객체를 폐기(Dispose)하고 다시 생성해서 Scence(씬, 장면)에 객체를 추가하면 됩니다. 그러나 너무 번거롭습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쉽게 처리하는 방법으로 객체의 Geometry(지오메트리, 기하 도형)만 폐기(Dispose)하고 변경되는 정보로 생성하여 객체의 Geometry(지오메트리, 기하 도형)에 적용하는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 테스트를 통해 확인해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. 구&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그룹(sphere1Group)에 있는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구 객체(sphere1)와 라인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;세그먼트즈&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;객체(sphere1WireframeLine)의 Geometry(지오메트리, 기하 도형)를 폐기(Dispose)합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692602683999&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 적용된 지오메트리 폐기
sphere1Group.children[0].geometry.dispose();
sphere1Group.children[1].geometry.dispose();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 새로운 SphereGeometry(스피어 지오메트리, 구 기하 도형)의&amp;nbsp;radius(반지름, 반경)는 기존과 동일하게 1로 하고 widthSegments(수평 세그먼트, 가로 분절 수)와 heightSegments(수직 세그먼트, 세로 분절 수)는 10으로 설정하여 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692602418536&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 새로운 지오메트리 생성
const sphere2Geometry = new THREE.SphereGeometry(1, 10, 10);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 새롭게 생성된 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;지오메트리(sphere2Geometry)를 구 객체(sphere1)의 Geometry(지오메트리, 기하 도형)에 적용시키고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;라인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;세그먼트즈&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;객체(sphere1WireframeLine)의 Geometry(지오메트리, 기하 도형)에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;지오메트리(sphere2Geometry)로 새로운 WireframeGeometry(와이어프레임&amp;nbsp;지오메트리)를 생성하여 적용시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692602844208&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 새로운 지오메트리 적용
sphere1Group.children[0].geometry = sphere2Geometry;
sphere1Group.children[1].geometry = new THREE.WireframeGeometry(sphere2Geometry);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c34rpj/btssc9ctCL7/kO2xhkiKVCcZ9aBS48R20K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c34rpj/btssc9ctCL7/kO2xhkiKVCcZ9aBS48R20K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c34rpj/btssc9ctCL7/kO2xhkiKVCcZ9aBS48R20K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc34rpj%2Fbtssc9ctCL7%2FkO2xhkiKVCcZ9aBS48R20K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 변경된 정보로 변경되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDE8cp/btsrIbnjaJv/FsJ6kmcEgAaMegtHSQkaeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDE8cp/btsrIbnjaJv/FsJ6kmcEgAaMegtHSQkaeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDE8cp/btsrIbnjaJv/FsJ6kmcEgAaMegtHSQkaeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDE8cp%2FbtsrIbnjaJv%2FFsJ6kmcEgAaMegtHSQkaeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그룹(sphere1Group)에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구 객체(sphere1)의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Geometry(지오메트리, 기하 도형)&lt;/span&gt;는 변경하지 않고 라인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;세그먼트즈&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;객체(sphere1WireframeLine)의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Geometry(지오메트리, 기하 도형)만 변경한다면 구의 객체의 모양과 와이어프레임 모양이 다르기 때문에 와이어프레임 선들이 구의 객체 안으로 들어가게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692603744905&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 적용된 지오메트리 폐기
//sphere1Group.children[0].geometry.dispose();
sphere1Group.children[1].geometry.dispose();

// 새로운 지오메트리 생성
const sphere2Geometry = new THREE.SphereGeometry(1, 10, 10);

// 새로운 지오메트리 적용
//sphere1Group.children[0].geometry = sphere2Geometry;
sphere1Group.children[1].geometry = new THREE.WireframeGeometry(sphere2Geometry);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGQUR2/btsrDf46Qcu/5kqO2WfwbyEmFBkAIgmzg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGQUR2/btsrDf46Qcu/5kqO2WfwbyEmFBkAIgmzg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGQUR2/btsrDf46Qcu/5kqO2WfwbyEmFBkAIgmzg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGQUR2%2FbtsrDf46Qcu%2F5kqO2WfwbyEmFBkAIgmzg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&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;그럼 Controls에 SphereGeometry(스피어 지오메트리, 구 기하 도형)의 속성들을 컨트롤러로 추가해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 테스트한 것처럼 매번 새로운 SphereGeometry(스피어 지오메트리, 구 기하 도형)를 생성해야 하기 때문에 parameters(파라미터)들을 별도의 객체로 관리해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. SphereGeometry(스피어 지오메트리, 구 기하 도형)를 생성할 때 사용되는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;parameters(파라미터)들로 객체(dataSphere1)를 만듭니다. (기본값으로 설정했습니다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692604573598&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const dataSphere1 = {
    radius: 1,
    widthSegments: 32,
    heightSegments: 16,
    phiStart: 0,
    phiLength: Math.PI * 2,
    thetaStart: 0,
    thetaLength: Math.PI
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SphereGeometry(스피어 지오메트리, 구 기하 도형)의 parameters(파라미터)를 객체(dataSphere1)로 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692604807443&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const sphere1Geometry = new THREE.SphereGeometry(dataSphere1.radius, dataSphere1.widthSegments, dataSphere1.heightSegments, dataSphere1.phiStart, dataSphere1.phiLength, dataSphere1.thetaStart, dataSphere1.thetaLength);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Material(머티리얼)에서 구 객체의 안쪽에도 렌더링 되도록 side 속성을 THREE.DoubleSide로 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692607335331&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const sphere1Material = new THREE.MeshStandardMaterial({color: 0x0000ff, side: THREE.DoubleSide});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nUw9A/btssgLBEO5h/XcmJUgekJY4o1BZ3ZTijaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nUw9A/btssgLBEO5h/XcmJUgekJY4o1BZ3ZTijaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nUw9A/btssgLBEO5h/XcmJUgekJY4o1BZ3ZTijaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnUw9A%2FbtssgLBEO5h%2FXcmJUgekJY4o1BZ3ZTijaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그룹(sphere1Group)에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;구 객체(sphere1)와 라인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;세그먼트즈&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;객체(sphere1WireframeLine)의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Geometry(지오메트리, 기하 도형)를 폐기(Dispose)하고 새로운 SphereGeometry(스피어 지오메트리, 구 기하 도형)를 생성하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;적용시키는 함수를 만듭니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692605427717&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 구 그룹 지오메트리 변경 함수
function updatesShereGroupGeometry() {

    sphere1Group.children[0].geometry.dispose();
    sphere1Group.children[1].geometry.dispose();

    const newSphereGeometry = new THREE.SphereGeometry(dataSphere1.radius, dataSphere1.widthSegments, dataSphere1.heightSegments, dataSphere1.phiStart, dataSphere1.phiLength, dataSphere1.thetaStart, dataSphere1.thetaLength);

    sphere1Group.children[0].geometry = newSphereGeometry;
    sphere1Group.children[1].geometry = new THREE.WireframeGeometry(newSphereGeometry);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4dRDI/btsshoGaAdS/9s98yZ2tkqvUpGUiD2I5Z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4dRDI/btsshoGaAdS/9s98yZ2tkqvUpGUiD2I5Z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4dRDI/btsshoGaAdS/9s98yZ2tkqvUpGUiD2I5Z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4dRDI%2FbtsshoGaAdS%2F9s98yZ2tkqvUpGUiD2I5Z1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제처럼 구 객체에 있는 속성들 컨트롤러로 추가해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 구 객체에 있는 속성들을 별도의 그룹으로 분리하기 위해서 Controls에 Folder(폴더)를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692605923549&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gui.addFolder(title)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;title(타이틀) : 폴더의 제목 표시줄에 표시할 이름입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제처럼 동일하게 폴더에 표시할 이름을 &quot;THREE.SphereGeometry&quot;으로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692605906840&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러에 구 그룹 객체를 위한 Folder(폴더) 추가
const folder1 = gui.addFolder('THREE.SphereGeometry');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XqjVX/btsshkX6lYN/X0ewHnqS7tGUJ51qrn0vb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XqjVX/btsshkX6lYN/X0ewHnqS7tGUJ51qrn0vb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XqjVX/btsshkX6lYN/X0ewHnqS7tGUJ51qrn0vb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXqjVX%2FbtsshkX6lYN%2FX0ewHnqS7tGUJ51qrn0vb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&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;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQDYz4/btsrEqynJOQ/d7fvcGkTKZUZD5Avl2lmSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQDYz4/btsrEqynJOQ/d7fvcGkTKZUZD5Avl2lmSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQDYz4/btsrEqynJOQ/d7fvcGkTKZUZD5Avl2lmSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQDYz4%2FbtsrEqynJOQ%2Fd7fvcGkTKZUZD5Avl2lmSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;객체(dataSphere1)로 컨트롤러에 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 컨트롤러에 $1(최소 값, 선택 값), max(최대 값)은 SphereGeometry(스피어 지오메트리, 구 기하 도형)의 기본값으로 설정합니다.&lt;/li&gt;
&lt;li&gt;컨트롤러가 변경될 때마다 구 그룹 지오메트리 변경 함수(updatesShereGroupGeometry)가 호출되도록 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1692606437210&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러 추가
// 구 지오메트리의 파라미터들로 추가
folder1.add(dataSphere1, 'radius', 1, 30).onChange(updatesShereGroupGeometry);
folder1.add(dataSphere1, 'widthSegments', 3, 64).step(1).onChange(updatesShereGroupGeometry);
folder1.add(dataSphere1, 'heightSegments', 2, 32).step(1).onChange(updatesShereGroupGeometry);
folder1.add(dataSphere1, 'phiStart', 0, Math.PI * 2).onChange(updatesShereGroupGeometry);
folder1.add(dataSphere1, 'phiLength', 0, Math.PI * 2).onChange(updatesShereGroupGeometry);
folder1.add(dataSphere1, 'thetaStart', 0, Math.PI * 2).onChange(updatesShereGroupGeometry);
folder1.add(dataSphere1, 'thetaLength', 0, Math.PI * 2).onChange(updatesShereGroupGeometry);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNcxBc/btssfzaYslK/YjZil6qT1Gn7mnnr2JSKq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNcxBc/btssfzaYslK/YjZil6qT1Gn7mnnr2JSKq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNcxBc/btssfzaYslK/YjZil6qT1Gn7mnnr2JSKq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNcxBc%2FbtssfzaYslK%2FYjZil6qT1Gn7mnnr2JSKq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;1668&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;1668&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;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boaCX5/btsrEqyofC0/gGRyw02Z14f8C3FBg0b1dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boaCX5/btsrEqyofC0/gGRyw02Z14f8C3FBg0b1dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boaCX5/btsrEqyofC0/gGRyw02Z14f8C3FBg0b1dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboaCX5%2FbtsrEqyofC0%2FgGRyw02Z14f8C3FBg0b1dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다양하게 컨트롤러의 값들을 변경시켜서 구 객체가 변경되는 것을 확인해 보시기 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJmLCc/btsrErxmVSk/yyI1PL5RHbwJyZw3i8v4wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJmLCc/btsrErxmVSk/yyI1PL5RHbwJyZw3i8v4wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJmLCc/btsrErxmVSk/yyI1PL5RHbwJyZw3i8v4wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJmLCc%2FbtsrErxmVSk%2FyyI1PL5RHbwJyZw3i8v4wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&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;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EBH7N/btsrB5Wez6X/VpOkxua4gLIhs4e6rJ7kvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EBH7N/btsrB5Wez6X/VpOkxua4gLIhs4e6rJ7kvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EBH7N/btsrB5Wez6X/VpOkxua4gLIhs4e6rJ7kvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEBH7N%2FbtsrB5Wez6X%2FVpOkxua4gLIhs4e6rJ7kvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&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;2548&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmwmMs/btsrTQCo5Lc/5P4eoYU7oNeKWGDkpMSKS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmwmMs/btsrTQCo5Lc/5P4eoYU7oNeKWGDkpMSKS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmwmMs/btsrTQCo5Lc/5P4eoYU7oNeKWGDkpMSKS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmwmMs%2FbtsrTQCo5Lc%2F5P4eoYU7oNeKWGDkpMSKS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1618&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>addFolder</category>
      <category>Controls</category>
      <category>geometry.dispose</category>
      <category>GUI</category>
      <category>lil-gui</category>
      <category>Three.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/301</guid>
      <comments>https://carrotweb.tistory.com/301#entry301comment</comments>
      <pubDate>Sun, 27 Aug 2023 20:40:13 +0900</pubDate>
    </item>
    <item>
      <title>three.js GUI Controls - lil-gui, gui.add</title>
      <link>https://carrotweb.tistory.com/300</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;three.js 사이트의 doc에서 Geometry(지오메트리, 기하 도형)들의 설명을 보면 오른쪽 상단에 Controls가 있습니다.&amp;nbsp; (&lt;a href=&quot;https://threejs.org/docs/index.html#api/en/geometries/SphereGeometry&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://threejs.org/docs/index.html#api/en/geometries/SphereGeometry&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qFN0B/btsrcrdumCh/bReSatuk3iVVViG96vynCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qFN0B/btsrcrdumCh/bReSatuk3iVVViG96vynCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qFN0B/btsrcrdumCh/bReSatuk3iVVViG96vynCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqFN0B%2FbtsrcrdumCh%2FbReSatuk3iVVViG96vynCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;1054&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1054&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Controls에는 객체의 속성 값들이 표시되고 속성 값을 변경하면 객체가 변경됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이번에 위의 예제 화면처럼 Controls를 만들어 주사위 객체의 Material(머티리얼)에 있는 alphaTest(알파&amp;nbsp;테스트) 속성을 변경해 보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이전에는 Controls를 만들기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;dat.GUI를 사용하였으나 최근에는 lil-gui를 대체하여 사용하고 있습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;three.js에서도 lil-gui를 모듈에 포함하여 배포하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controls를 만들기 위해서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;lil-gui(릴 지유아이&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;릴&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그래픽 사용자 인터페이스) 모듈을 가져와야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;lil-gui(릴 지유아이,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;릴&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그래픽 사용자 인터페이스)는 사용자가 웹 페이지에서 객체의 속성 값들을 변경할 수 있게 플로팅 패널(Floating Panel, 떠 있는 패널)로 컨트롤러(Controller) UI를 만들어 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 설명은 lil-gui 사이트(&lt;a href=&quot;https://lil-gui.georgealways.com/&quot;&gt;https://lil-gui.georgealways.com/&lt;/a&gt;)를 참고하시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. import를 통해 lil-gui(릴 지유아이&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;릴&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그래픽 사용자 인터페이스) 모듈을 가져옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692103012834&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { GUI } from 'three/addons/libs/lil-gui.module.min.js';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBF83/btsrH4OwJua/fQK2SGD1UeYF3HtEHQXk91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBF83/btsrH4OwJua/fQK2SGD1UeYF3HtEHQXk91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBF83/btsrH4OwJua/fQK2SGD1UeYF3HtEHQXk91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUBF83%2FbtsrH4OwJua%2FfQK2SGD1UeYF3HtEHQXk91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2676&quot; height=&quot;1664&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&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;lil-gui 모듈의 실제 위치는 &quot;\node_modules\three\examples\jsm\libs\lil-gui.module.min.js&quot; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVR3N3/btsrf5HYtEG/yMyfzFqyWrUtmLkCXQ2X31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVR3N3/btsrf5HYtEG/yMyfzFqyWrUtmLkCXQ2X31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVR3N3/btsrf5HYtEG/yMyfzFqyWrUtmLkCXQ2X31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVR3N3%2Fbtsrf5HYtEG%2FyMyfzFqyWrUtmLkCXQ2X31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2676&quot; height=&quot;1660&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. lil-gui(릴&amp;nbsp;지유아이,&amp;nbsp;릴&amp;nbsp;그래픽&amp;nbsp;사용자&amp;nbsp;인터페이스)를&amp;nbsp;생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692111348277&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GUI 생성
const gui = new GUI();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcP4IU/btsrB3XFmxO/hmyJYu2OGNZzLEswbvvKT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcP4IU/btsrB3XFmxO/hmyJYu2OGNZzLEswbvvKT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcP4IU/btsrB3XFmxO/hmyJYu2OGNZzLEswbvvKT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcP4IU%2FbtsrB3XFmxO%2FhmyJYu2OGNZzLEswbvvKT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2676&quot; height=&quot;1664&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&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;그러면 오른쪽 상단에 Controls가 생성되어 나타납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QOCU8/btsrjsCcsvH/QkvjTqKkSJcBCgaIS7EMEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QOCU8/btsrjsCcsvH/QkvjTqKkSJcBCgaIS7EMEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QOCU8/btsrjsCcsvH/QkvjTqKkSJcBCgaIS7EMEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQOCU8%2FbtsrjsCcsvH%2FQkvjTqKkSJcBCgaIS7EMEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&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;3. Controls에 컨트롤러를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692112497450&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gui.add(object, property, [$1], [max], [step])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;object(객체) : 컨트롤러가 제어할 객체입니다.&lt;/li&gt;
&lt;li&gt;property(프로퍼티, 속성) : 객체에서 제어할 속성 이름입니다.&lt;/li&gt;
&lt;li&gt;$1(최소 값, 선택 값) : 숫자 컨트롤러의 최소 값이거나 드롭다운에서 선택 가능한 값입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;max(최대 값) : 숫자 컨트롤러의 최대 값입니다.&lt;/li&gt;
&lt;li&gt;step(스탭, 간격) : 숫자 컨트롤러의 간격 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;object(객체)는 주사위 육면체 객체에 적용되어 있는 Material(머티리얼) 배열에서 첫 번째 배열을 설정하고 property(프로퍼티, 속성)는 alphaTest(알파 테스트)로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692112445788&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러 추가
// 주사위 육면체 객체에 적용되어 있는 머티리얼 객체, 프로퍼티는 alphaTest로 설정
gui.add(box2Materials[0], 'alphaTest');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pp3An/btsrEpZJ4t7/OriSH9deVYxIHtm0alAUM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pp3An/btsrEpZJ4t7/OriSH9deVYxIHtm0alAUM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pp3An/btsrEpZJ4t7/OriSH9deVYxIHtm0alAUM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPp3An%2FbtsrEpZJ4t7%2FOriSH9deVYxIHtm0alAUM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2676&quot; height=&quot;1664&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&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;그러면 오른쪽 상단에 있는 Controls에 alphaTest라는 이름(속성 이름)으로 숫자 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;컨트롤러가 추가됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q4A83/btsrgjMNLkr/ABEGalpqcqsc3RbDQ4gEj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q4A83/btsrgjMNLkr/ABEGalpqcqsc3RbDQ4gEj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q4A83/btsrgjMNLkr/ABEGalpqcqsc3RbDQ4gEj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq4A83%2FbtsrgjMNLkr%2FABEGalpqcqsc3RbDQ4gEj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaTest 숫자 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;컨트롤러에는 현재 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt; 속성 값이 나타납니다. (현재 속성 값은 0.1입니다.)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;속성 값을 입력하거나 상/하 화살표 키보드를 눌러 값을 변경할 수 있습니다. (상/하 화살표 키보드를 누르면 1 간격으로 변경됩니다.)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;맨 위의 예제 화면처럼&lt;span&gt; 숫자 컨트롤러에 슬라이더(Sliders)를 추가하기 위해서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$1(최소 값)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;max(최대 값)를 추가하면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaTest(알파 테스트) 속성 값인 알파 값(0 ~ 1)으로&amp;nbsp;$1(최소 값)과 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;max(최대 값)를 설정하고 step(스탭, 간격)으로 0.01로 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692113628352&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러 추가
// 주사위 육면체 객체에 적용되어 있는 머티리얼 객체, 프로퍼티는 alphaTest로 설정
// 최소 값은 0, 최대 값은 1, 간격은 0.01로 설정
gui.add(box2Materials[0], 'alphaTest', 0, 1, 0.01);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clsv4d/btsrCxxqEZn/ulTvF8xrUksnJRaGghbgG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clsv4d/btsrCxxqEZn/ulTvF8xrUksnJRaGghbgG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clsv4d/btsrCxxqEZn/ulTvF8xrUksnJRaGghbgG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclsv4d%2FbtsrCxxqEZn%2FulTvF8xrUksnJRaGghbgG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2676&quot; height=&quot;1664&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&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;또는 min(), max(), step() 메서드를 사용하여 설정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692114750342&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gui.add(box2Materials[0], 'alphaTest').min(0).max(1).step(0.01);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨트롤러에 슬라이더(Sliders)가 추가됩니다. 그리고 슬라이더(Sliders)를 마우스로 이동시키면 step(스탭, 간격) 값만큼 변경됩니다. (상/하 화살표 키보드를 눌러도 step 값만큼 변경됩니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkOUdn/btsq51foO7F/rQSjI1PFMeKLieyAeN49Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkOUdn/btsq51foO7F/rQSjI1PFMeKLieyAeN49Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkOUdn/btsq51foO7F/rQSjI1PFMeKLieyAeN49Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkOUdn%2Fbtsq51foO7F%2FrQSjI1PFMeKLieyAeN49Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨트롤러의 이름은 name() 메서드로 변경할 수 있습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨트롤러의 이름을 &quot;알파 테스트&quot;로 변경하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692115256661&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러 추가
// 주사위 육면체 객체에 적용되어 있는 머티리얼 객체, 프로퍼티는 alphaTest로 설정
// 최소 값은 0, 최대 값은 1, 간격은 0.01로 설정
// 칸트롤러의 이름을 '알파 테스트'로 변경
gui.add(box2Materials[0], 'alphaTest', 0, 1, 0.01).name('알파 테스트');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/15Ifr/btsrCR3xgz5/zcpnobetyJk1brjSh7O2y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/15Ifr/btsrCR3xgz5/zcpnobetyJk1brjSh7O2y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/15Ifr/btsrCR3xgz5/zcpnobetyJk1brjSh7O2y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F15Ifr%2FbtsrCR3xgz5%2FzcpnobetyJk1brjSh7O2y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2676&quot; height=&quot;1664&quot; data-origin-width=&quot;2676&quot; data-origin-height=&quot;1664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨트롤러의 이름이 &quot;알파 테스트&quot;로 변경됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P81Sg/btsrgUzcNOo/krrhZKxEAKkqhOvnBToRE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P81Sg/btsrgUzcNOo/krrhZKxEAKkqhOvnBToRE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P81Sg/btsrgUzcNOo/krrhZKxEAKkqhOvnBToRE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP81Sg%2FbtsrgUzcNOo%2FkrrhZKxEAKkqhOvnBToRE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;컨트롤러의 슬라이더(Sliders)를 마우스로 이동시켜 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt; &lt;/span&gt;&lt;/span&gt;속성 값을 0으로 변경시키면 5번 주사위 이미지가 투명하지 않게 되고 그림자도 투명하지 않게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh8yHQ/btsq5b3DYoG/Olz5IzMCJiUVkVuxWtGDs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh8yHQ/btsq5b3DYoG/Olz5IzMCJiUVkVuxWtGDs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh8yHQ/btsq5b3DYoG/Olz5IzMCJiUVkVuxWtGDs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh8yHQ%2Fbtsq5b3DYoG%2FOlz5IzMCJiUVkVuxWtGDs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;속성 값을 1로 변경시키면 5번 주사위 이미지가 투명해지지만 Aliasing(앨리어싱)이 발생되어 5번 주사위 이미지가 각지게 되고 그림자는 없어지게 됩니다. (alphaTest&amp;nbsp;속성이&amp;nbsp;1이&amp;nbsp;되면&amp;nbsp;흰색까지&amp;nbsp;투과되지&amp;nbsp;않습니다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l8XSw/btsrcq6LuNK/hSOCJMf2Zr36Axyzvmx6N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8XSw/btsrcq6LuNK/hSOCJMf2Zr36Axyzvmx6N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8XSw/btsrcq6LuNK/hSOCJMf2Zr36Axyzvmx6N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8XSw%2Fbtsrcq6LuNK%2FhSOCJMf2Zr36Axyzvmx6N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;속성&lt;span&gt; 값의 변화에 따라 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;주사위 이미지와 그림자가 변화되는 것을&lt;span&gt; &lt;/span&gt;&lt;/span&gt;좀 더 명확하게 확인하기 위해서 주사위 육면체 객체에 적용되어 있는 Material(머티리얼) 배열에서 첫 번째 배열에 있는 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;alphaMap(알파 맵)을 이전에 추가한 그라데이션된 이미지 파일(dicemap.png)로 변경하여 테스트 헤보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692525982832&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5.png&quot;), alphaMap: box2TextureLoader.load(&quot;resources/textures/dicemap.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[alphaTest&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;속성 값을 0.1로 변경할 경우]&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpfLhK/btsq2i9IpK2/fNHuEtFsNUP1uvEZLiFh0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpfLhK/btsq2i9IpK2/fNHuEtFsNUP1uvEZLiFh0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpfLhK/btsq2i9IpK2/fNHuEtFsNUP1uvEZLiFh0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpfLhK%2Fbtsq2i9IpK2%2FfNHuEtFsNUP1uvEZLiFh0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[alphaTest&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;속성 값을 0.54로 변경할 경우]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GpHkW/btsrf3XHKM0/zF4D7sC7vkADWzS9SOOD30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GpHkW/btsrf3XHKM0/zF4D7sC7vkADWzS9SOOD30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GpHkW/btsrf3XHKM0/zF4D7sC7vkADWzS9SOOD30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGpHkW%2Fbtsrf3XHKM0%2FzF4D7sC7vkADWzS9SOOD30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[alphaTest&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;속성 값을 0.9로 변경할 경우]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GLYyG/btsrgpzwP1k/T3AKil0Lio3UbLdarEpRGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GLYyG/btsrgpzwP1k/T3AKil0Lio3UbLdarEpRGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GLYyG/btsrgpzwP1k/T3AKil0Lio3UbLdarEpRGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGLYyG%2FbtsrgpzwP1k%2FT3AKil0Lio3UbLdarEpRGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;1624&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;1624&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;</description>
      <category>Three.js/기초튼튼</category>
      <category>Controls</category>
      <category>GUI</category>
      <category>lil-gui</category>
      <category>Three.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/300</guid>
      <comments>https://carrotweb.tistory.com/300#entry300comment</comments>
      <pubDate>Sun, 20 Aug 2023 19:10:37 +0900</pubDate>
    </item>
    <item>
      <title>three.js 텍스처 매핑, 알파 맵과 알파 테스트로 이미지 자르기와 그림자 처리 - alphaMap, alphaTest</title>
      <link>https://carrotweb.tistory.com/299</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 배경이 투명하지 않는 주사위 이미지를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Material(머티리얼)의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;alphaMap(알파 맵) 속성을 사용하여 이미지를 자르고 반투명하게 처리해 보겠습니다. 그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest(알파 테스트)&lt;span&gt; 속성을 사용하여 처리되는 범위를 조정해 보겠습니다. (&lt;/span&gt;&lt;/span&gt;그림자는 자동으로 이미지에 맞게 처리됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 alphaMap(알파 맵) 속성에 대해 간단하게 알아보고 진행하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaMap(알파 맵)은 흰색과 검은색 그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;흰색과 검은색 사이의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;회색(밝은 회색부터 어두운 회색까지)으로 구성된 이미지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaMap(알파 맵)에 있는 흰색은 이미지 처리 시 원본 이미지가 그대로 투과됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaMap(알파 맵)에 있는&amp;nbsp;&lt;/span&gt;검은색은 이미지 처리 시 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;원본 이미지가&lt;span&gt; 투과되지 않습니다. (검은색과 같은 위치에 있는 이미지는 삭제 처리됩니다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaMap(알파 맵)에 있는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;흰색과 검은색 사이의 &lt;/span&gt;&lt;/span&gt;회색(밝은 회색부터 어두운 회색까지)은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이미지 처리 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;반투명하게 투과됩니다. (회색이 어두울 수 록 투명해집니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, alphaMap(알파 맵)은 검은색의 농도에 따라 이미지 처리 시 삭제(검은색)하거나 반투명(밝은 회색부터 어두운 회색까지 농도에 따라 투명도가 달라짐)으로 처리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 alphaTest(알파 테스트) 속성 값인 알파 값(0.1 ~ 0.9)에 따라 회색(밝은 회색부터 어두운 회색까지)의 처리되는 범위가 넓어집니다. (기본적으로 alphaTest 속성은 0입니다. 그리고 alphaTest 속성이 1이 되면 흰색까지 투과되지 않습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 알파 값이 작을수록 alphaMap(알파 맵)에서 어두운 회색들은 투과되지 않습니다. 반대로 알파 값이 클수록 밝은 회색까지도 투과되지 않습니다.&lt;/p&gt;
&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;PPT로 alphaMap(알파 맵)으로 사용할 이미지를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;간단하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;만들어 보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주사위 이미지와 같은 크기(가로/세로는 3cm)의 도형(직사각형)을 생성합니다.&lt;/li&gt;
&lt;li&gt;도형 윤곽선은 &quot;윤곽선 없음&quot;으로 설정합니다.&lt;/li&gt;
&lt;li&gt;도형을 선택하고 콘텍스트 메뉴에서 &quot;도형 서식&quot;을 클릭합니다.&lt;/li&gt;
&lt;li&gt;도형 서식에서 &quot;그라데이션 채우기&quot;를 선택하고 그라데이션 중지점에서 첫 번째 중지점을 선택하고 색은 검은색으로 선택합니다. 그리고 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;그라데이션 중지점에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;세 번째 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;중지점을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;선택하고&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;색은&lt;span&gt;&amp;nbsp;흰색&lt;/span&gt;&lt;/span&gt;으로 선택합니다.&lt;/li&gt;
&lt;li&gt;도형을 PNG 파일로 저장합니다. (저는 dicemap.png으로 저장하였습니다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lopl8/btsq1b2CBLc/9mHc5gZ5VQzfvVKkRwIWYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lopl8/btsq1b2CBLc/9mHc5gZ5VQzfvVKkRwIWYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lopl8/btsq1b2CBLc/9mHc5gZ5VQzfvVKkRwIWYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flopl8%2Fbtsq1b2CBLc%2F9mHc5gZ5VQzfvVKkRwIWYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;556&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;1050&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;프로젝트의 resources \ textures 폴더에 alphaMap(알파 맵)으로 사용할 이미지(dicemap.png) 파일을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjQUX1/btsrdjLWLNV/ye2xjysFPREUJk9iGaQfV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjQUX1/btsrdjLWLNV/ye2xjysFPREUJk9iGaQfV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjQUX1/btsrdjLWLNV/ye2xjysFPREUJk9iGaQfV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjQUX1%2FbtsrdjLWLNV%2Fye2xjysFPREUJk9iGaQfV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2672&quot; height=&quot;1656&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&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;테스트를 위해 Material(머티리얼) 배열에서 5번 주사위 이미지를 배경이 투명하지 않는 주사위 이미지(dice5.png)로 변경하고 추가된 이미지 파일(dicemap.png)을 TextureLoader(텍스처로더)로 로드하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaMap 속성 값으로&lt;span&gt; 설정합니다. 그리고 alphaTest 속성 값을 0으로 설정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1691936378127&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5.png&quot;), alphaMap: box2TextureLoader.load(&quot;resources/textures/dicemap.png&quot;), side: THREE.DoubleSide, alphaTest: 0, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BcbqN/btsq1av7Je5/qfklETxve6BZFHe0CACwnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BcbqN/btsq1av7Je5/qfklETxve6BZFHe0CACwnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BcbqN/btsq1av7Je5/qfklETxve6BZFHe0CACwnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBcbqN%2Fbtsq1av7Je5%2FqfklETxve6BZFHe0CACwnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2672&quot; height=&quot;1656&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&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;마우스로 이동해서 보시면 5번 주사위 이미지가 alphaMap(알파 맵)으로 이미지 처리가 되어서 alphaMap(알파 맵)에서 검은색 영역은 삭제처리되고 회색(밝은 회색부터 어두운 회색까지) 영역은 반투명하게 처리되어 나오는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sURLe/btsq0vm1oMh/yksvSANV6ThV4ZrRFpA4A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sURLe/btsq0vm1oMh/yksvSANV6ThV4ZrRFpA4A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sURLe/btsq0vm1oMh/yksvSANV6ThV4ZrRFpA4A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsURLe%2Fbtsq0vm1oMh%2FyksvSANV6ThV4ZrRFpA4A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2560&quot; height=&quot;1632&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1632&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;alphaTest(알파 테스트) 속성 값인 알파 값(0.1 ~ 0.9)에 따라 다음처럼 처리됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ouxuk/btsq0xrzsUK/8F9hKABGbhsiHwIcsNLO41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ouxuk/btsq0xrzsUK/8F9hKABGbhsiHwIcsNLO41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ouxuk/btsq0xrzsUK/8F9hKABGbhsiHwIcsNLO41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fouxuk%2Fbtsq0xrzsUK%2F8F9hKABGbhsiHwIcsNLO41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1410&quot; height=&quot;636&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림자도 처리된 이미지에 맞게 나타납니다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaMap(알파 맵) 이미지가 그라데이션으로 되어있어 처리된 이미지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;크기만큼&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;그림자가 나오게 됩니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그럼 5번 주사위 이미지(dice5.png)에 맞게 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaMap(알파 맵) 이미지를 만들어 처리해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;5번 주사위 이미지를 만든 PPT에서 도형의 윤곽선 색을 흰색으로 변경합니다. (윤관선 색이 검은색이면 원본 이미지에 있는 윤관선이 투과되지 않기 때문입니다.)&lt;/li&gt;
&lt;li&gt;검은색 점 도형은 흰색으로 흰색 배경은 검은색으로 변경합니다. (흰색만 투과되기 때문입니다.)&lt;/li&gt;
&lt;li&gt;도형을 PNG 파일로 저장합니다. (기존 파일명에 'm'를 추가하여 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;dice5m&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;.png으로 저장하였습니다.)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4bCdx/btsq8Ik4a9d/NAIomSbXFlzDYG4jsLKABK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4bCdx/btsq8Ik4a9d/NAIomSbXFlzDYG4jsLKABK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4bCdx/btsq8Ik4a9d/NAIomSbXFlzDYG4jsLKABK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4bCdx%2Fbtsq8Ik4a9d%2FNAIomSbXFlzDYG4jsLKABK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;100&quot; height=&quot;137&quot; data-origin-width=&quot;178&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 resources \ textures 폴더에 alphaMap(알파 맵)으로 사용할 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5번 주사위 이미지&lt;/span&gt;(dice5m.png) 파일을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3V5fW/btsq1JE3MD8/Llhm9csQUYWVM4zqLiEHJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3V5fW/btsq1JE3MD8/Llhm9csQUYWVM4zqLiEHJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3V5fW/btsq1JE3MD8/Llhm9csQUYWVM4zqLiEHJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3V5fW%2Fbtsq1JE3MD8%2FLlhm9csQUYWVM4zqLiEHJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2672&quot; height=&quot;1656&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Material(머티리얼) 배열에서 추가된 이미지 파일(dice5m.png)로 alphaMap 속성의 값을 변경합니다. 그리고 alphaTest 속성 값을 0.1로 설정합니다. (alphaTest 속성 값을 0으로 설정하면 투명처리가 되지 않습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1691940322541&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5.png&quot;), alphaMap: box2TextureLoader.load(&quot;resources/textures/dice5m.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W8Oa3/btsrdjrFOCv/nvd0qiU9tUoqjodUDH8rV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W8Oa3/btsrdjrFOCv/nvd0qiU9tUoqjodUDH8rV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W8Oa3/btsrdjrFOCv/nvd0qiU9tUoqjodUDH8rV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW8Oa3%2FbtsrdjrFOCv%2Fnvd0qiU9tUoqjodUDH8rV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2672&quot; height=&quot;1656&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러면 5번 주사위 이미지가 투명하게 처리되어 보이게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGrcMD/btsq5cfORHG/k2eSuoQdodvFnIVqyKT1SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGrcMD/btsq5cfORHG/k2eSuoQdodvFnIVqyKT1SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGrcMD/btsq5cfORHG/k2eSuoQdodvFnIVqyKT1SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGrcMD%2Fbtsq5cfORHG%2Fk2eSuoQdodvFnIVqyKT1SK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2560&quot; height=&quot;1632&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;참고로, 어도비 포토샵(Photoshop)에서도 alphaMap(알파 맵)과 같은 방식으로 이미지의 배경을 투명하게 하거나 반투명하게 처리하여 여러 이미지들을 겹칠 때 사용합니다. 이런 방식을 alpha channel(알파 채널)이라고 합니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>AlphaMap</category>
      <category>AlphaTest</category>
      <category>Three.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/299</guid>
      <comments>https://carrotweb.tistory.com/299#entry299comment</comments>
      <pubDate>Mon, 14 Aug 2023 00:55:48 +0900</pubDate>
    </item>
    <item>
      <title>three.js 텍스처 매핑, 이미지 투명처리, 투명한 그림자 처리 - transparent, alphaTest, THREE.DoubleSide</title>
      <link>https://carrotweb.tistory.com/298</link>
      <description>&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;주사위 이미지를 만든 PPT에서 주사위 틀의 도형 채우기를 &quot;채우기 없음&quot;으로 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각각을 PNG 파일로 저장합니다. (기존 파일명에 't'를 추가했습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCLjO/btsoyaFQ2E0/l3ajIy0xQOjKkAgEkwsB91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCLjO/btsoyaFQ2E0/l3ajIy0xQOjKkAgEkwsB91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCLjO/btsoyaFQ2E0/l3ajIy0xQOjKkAgEkwsB91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCLjO%2FbtsoyaFQ2E0%2Fl3ajIy0xQOjKkAgEkwsB91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;139&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;245&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;프로젝트의 resources \ textures 폴더에 주사위 이미지 파일을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckOAnM/btsozknG5Bw/nSUjKMywuax1bdTkUEMUT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckOAnM/btsozknG5Bw/nSUjKMywuax1bdTkUEMUT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckOAnM/btsozknG5Bw/nSUjKMywuax1bdTkUEMUT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckOAnM%2FbtsozknG5Bw%2FnSUjKMywuax1bdTkUEMUT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1648&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material(머티리얼) 배열에서 기존 이미지를 추가된 주사위 이미지로 변경하고 주사위 이미지의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;체크무늬 부분이&lt;/span&gt;&amp;nbsp;투명하게 처리되도록 transparent 속성을 true로 추가합니다. (기본적으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;transparent&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;속성은 false입니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1690117660407&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5t.png&quot;), transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2t.png&quot;), transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1t.png&quot;), transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6t.png&quot;), transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3t.png&quot;), transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4t.png&quot;), transparent: true})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du1ghx/btsp3nRmjLo/Tsnyw1e9LJXHozo3JwXVeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du1ghx/btsp3nRmjLo/Tsnyw1e9LJXHozo3JwXVeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du1ghx/btsp3nRmjLo/Tsnyw1e9LJXHozo3JwXVeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu1ghx%2Fbtsp3nRmjLo%2FTsnyw1e9LJXHozo3JwXVeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1652&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 주사위가 투명하게 처리되어 바닥과 그림자가 보이게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2626&quot; data-origin-height=&quot;1642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vTQph/btsoxE1xJQE/d3ex8Fiok3jQojqqf8bZjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vTQph/btsoxE1xJQE/d3ex8Fiok3jQojqqf8bZjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vTQph/btsoxE1xJQE/d3ex8Fiok3jQojqqf8bZjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvTQph%2FbtsoxE1xJQE%2Fd3ex8Fiok3jQojqqf8bZjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2626&quot; height=&quot;1642&quot; data-origin-width=&quot;2626&quot; data-origin-height=&quot;1642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스로 이동해서 보면 주사위 뒤에 있는 객체가 보이는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2626&quot; data-origin-height=&quot;1642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3AuL8/btsoxdbYeAk/qW4ArqAeFPNY5CUNTUYaqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3AuL8/btsoxdbYeAk/qW4ArqAeFPNY5CUNTUYaqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3AuL8/btsoxdbYeAk/qW4ArqAeFPNY5CUNTUYaqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3AuL8%2FbtsoxdbYeAk%2FqW4ArqAeFPNY5CUNTUYaqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2626&quot; height=&quot;1642&quot; data-origin-width=&quot;2626&quot; data-origin-height=&quot;1642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만, 2가지가 투명하게 처리되지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째, 주사위의 그림자가 투명하지 않게 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째, 주사위안이 투명하지 않게 나옵니다. (위를 보면 3번 주사위 왼쪽에 있는 2번, 바닥에 있는 6번, 뒤에 있는 4번 이미지가 보이지 않습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&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;여기서는 텍스처를 사용하는 Material(머티리얼)에서 처리할 수 있는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 투명하지 않는 텍스처와 alphaMap(알파 맵), alphaTest(알파 테스트)를 사용하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 투명한 텍스처와 alphaTest(알파 테스트)를 사용하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주사위 이미지가 투명하기 때문에 우리는 2번째 방법으로 alphaTest(알파 테스트) 속성을 추가하면 간단하게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 alphaTest(알파 테스트) 속성에 대해 알아보고 진행하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material(머티리얼)의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest 속성은 알파 테스트를 실행할 때 사용할 알파 값으로&lt;/span&gt; 0.0부터 1.0까지 값을 가질 수 있습니다. (주의할 점은 Material(머티리얼)의 opacity 속성을 사용하고 있다면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;opacity&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;값이 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;값 보다 작으면 렌더링이 되지 않습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;알파 테스트)는 이미지 조각(픽셀)의 알파 값을 비교하여 조각(픽셀)을 삭제처리 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaTest(알파 테스트)를 위해 주사위 이미지 보다 복잡한 나무 이미지를 사용하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;값에 따라 어떻게 이미지가 삭제 처리되는지 확인해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alphaTest(알파 테스트)할 나무 이미지(배경이 투명)입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ZAsN/btsp7U8AeJP/mKXHLaf2mlbDtKOv4bumCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ZAsN/btsp7U8AeJP/mKXHLaf2mlbDtKOv4bumCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ZAsN/btsp7U8AeJP/mKXHLaf2mlbDtKOv4bumCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ZAsN%2Fbtsp7U8AeJP%2FmKXHLaf2mlbDtKOv4bumCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;236&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Material(머티리얼)의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;alphaTest 값을 각각 0.1, 0.5, 0.9로 설정하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1691242320592&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new THREE.MeshStandardMaterial({map: textureLoader.load(&quot;resources/textures/tree1.png&quot;), alphaTest: 0.1, transparent: true});

new THREE.MeshStandardMaterial({map: textureLoader.load(&quot;resources/textures/tree1.png&quot;), alphaTest: 0.5, transparent: true});

new THREE.MeshStandardMaterial({map: textureLoader.load(&quot;resources/textures/tree1.png&quot;), alphaTest: 0.9, transparent: true});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clYpq6/btsqfA819cS/E8DDiQCkCxtwoHxRdl1v01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clYpq6/btsqfA819cS/E8DDiQCkCxtwoHxRdl1v01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clYpq6/btsqfA819cS/E8DDiQCkCxtwoHxRdl1v01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclYpq6%2FbtsqfA819cS%2FE8DDiQCkCxtwoHxRdl1v01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1298&quot; height=&quot;315&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위의 결과처럼 alphaTest&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;값이 높을수록 더 많은 부분이 삭제가 되는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고 계단 현상인 Aliasing(앨리어싱)이 발생되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이미지가 각지게 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그래서 이미지에 맞게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;alphaTest&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;값을&lt;span&gt; &lt;/span&gt;&lt;/span&gt;설정해야 합니다.&amp;nbsp;일반적으로 0.1에서 0.5 이하로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;각각의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Material(머티리얼)&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;에 &lt;/span&gt;&lt;/span&gt;alphaTest&lt;span&gt; 속성&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;을&lt;span&gt; 0.1로 추가합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690724971004&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5t.png&quot;), alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2t.png&quot;), alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1t.png&quot;), alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6t.png&quot;), alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3t.png&quot;), alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4t.png&quot;), alphaTest: 0.1, transparent: true})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjvPKK/btsp35wh9rY/FwrcabyxNqveZImLa6lCW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjvPKK/btsp35wh9rY/FwrcabyxNqveZImLa6lCW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjvPKK/btsp35wh9rY/FwrcabyxNqveZImLa6lCW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjvPKK%2Fbtsp35wh9rY%2FFwrcabyxNqveZImLa6lCW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1652&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 주사위 이미지가 투과되어 보이게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q4uFO/btsp9YJfxH7/9PUd1DBk76r4keQKeR8TzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q4uFO/btsp9YJfxH7/9PUd1DBk76r4keQKeR8TzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q4uFO/btsp9YJfxH7/9PUd1DBk76r4keQKeR8TzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ4uFO%2Fbtsp9YJfxH7%2F9PUd1DBk76r4keQKeR8TzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2576&quot; height=&quot;1630&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1630&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;2576&quot; data-origin-height=&quot;1630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duAgEf/btsqfzI4ycj/A549hFNoLVUocb4W9ztiLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duAgEf/btsqfzI4ycj/A549hFNoLVUocb4W9ztiLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duAgEf/btsqfzI4ycj/A549hFNoLVUocb4W9ztiLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduAgEf%2FbtsqfzI4ycj%2FA549hFNoLVUocb4W9ztiLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2576&quot; height=&quot;1630&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1630&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;그러나 자세히 보면 투과된 그림자의 주사위 이미지는 오른쪽에 있는 5번, 바닥에 있는 6번, 뒤에 있는 4번 이미지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주사위의 1번, 2번, 3번은 왜 투과되어 나오지 않는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 위의 두 번째 문제인 주사위안이 투명하지 않게 나오는 현상과 같은 문제입니다.&lt;/p&gt;
&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;기본적으로 Material(머티리얼)에서는 텍스처를 렌더링 할 때 앞면(THREE.FrontSide)만 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그래서 우리가 사용한 육면체는 전부 앞면만 렌더링 된 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그렇기 때문에 뒷면(THREE.BackSide)이 없는 주사위의 1번(위), 2번(왼쪽), 3번(앞쪽)은 그림자로 투과되지 않았습니다. &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고&amp;nbsp; 주사위의 4번(뒤), 5번(오른쪽), 6번(바닥)도 뒷면(THREE.BackSide)이 없어 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;주사위안에서&lt;span&gt;&amp;nbsp;보이지 않았던 겁니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그래서 Material(머티리얼)에서 렌더링 할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 면을 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앞면과 뒷면 둘 다 되게 처리하면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;각각의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Material(머티리얼)에서 주사위 이미지가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앞면과 뒷면에서 &lt;span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;렌더링 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;되도록 side 속성을 THREE.DoubleSide로 추가합니다. (기본적으로&lt;span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;side&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;속성은 THREE.FrontSide입니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1691246108449&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4t.png&quot;), side: THREE.DoubleSide, alphaTest: 0.1, transparent: true})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwUYwT/btsqd3pZXiz/knRIv5TSamHHtH7bG9Yl30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwUYwT/btsqd3pZXiz/knRIv5TSamHHtH7bG9Yl30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwUYwT/btsqd3pZXiz/knRIv5TSamHHtH7bG9Yl30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwUYwT%2Fbtsqd3pZXiz%2FknRIv5TSamHHtH7bG9Yl30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1652&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 주사위 안도 그림자도 모두 이미지가 투과되어 보이게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGL4pe/btsp7JsyUPq/5Gj5bkN17kUIWSJD71vA01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGL4pe/btsp7JsyUPq/5Gj5bkN17kUIWSJD71vA01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGL4pe/btsp7JsyUPq/5Gj5bkN17kUIWSJD71vA01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGL4pe%2Fbtsp7JsyUPq%2F5Gj5bkN17kUIWSJD71vA01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2576&quot; height=&quot;1630&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1630&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;2576&quot; data-origin-height=&quot;1630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCzi8B/btsp7BakHS6/e9pIJfHKKth1ebI3RzYtp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCzi8B/btsp7BakHS6/e9pIJfHKKth1ebI3RzYtp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCzi8B/btsp7BakHS6/e9pIJfHKKth1ebI3RzYtp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCzi8B%2Fbtsp7BakHS6%2Fe9pIJfHKKth1ebI3RzYtp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2576&quot; height=&quot;1630&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1630&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;</description>
      <category>Three.js/기초튼튼</category>
      <category>AlphaTest</category>
      <category>THREE.BackSide</category>
      <category>THREE.DoubleSide</category>
      <category>THREE.FrontSide</category>
      <category>Three.js</category>
      <category>transparent</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/298</guid>
      <comments>https://carrotweb.tistory.com/298#entry298comment</comments>
      <pubDate>Sat, 5 Aug 2023 23:49:41 +0900</pubDate>
    </item>
    <item>
      <title>three.js 텍스처 매핑, 이미지 입히기  - TextureLoader</title>
      <link>https://carrotweb.tistory.com/297</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Texture(텍스처)란 객체의 색상과 질감을 표현하는 Material(머티리얼)에서 사용되는 이미지나 비디오를 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Texture Mapping(텍스처 매핑)이란 객체의 표면에 색을 칠하거나 이미지를 입히는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에 Texture Mapping(텍스처 매핑)하기 위해서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Texture(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;텍스처&lt;span&gt;)를 로드해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;three.js에서는 TextureLoader(텍스처로더)로 Texture(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;텍스처&lt;span&gt;)를 로드합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextureLoader(텍스처로더)는 이미지 (PNG, JPG, GIF, DDS) 또는 비디오 (MP4, OGG/OGV)를 로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1689002207476&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextureLoader(manager : LoadingManager)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;manager(매니저) : 로딩매니저입니다. 기본값 THREE.DefaultLoadingManage&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 두 번째 육면체 객체에 Texture Mapping(텍스처 매핑)을 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;육면체 객체에 Texture(텍스처 매핑)할 나무 바닥 이미지를 다운로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 나무 바닥 이미지를 &lt;a href=&quot;https://www.freepik.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Freepik 사이트&lt;/a&gt;에서 무료로 다운로드하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2488&quot; data-origin-height=&quot;1636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FLVec/btsm4C43CH4/H5u6g61pYZg7u0HCzTAB3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FLVec/btsm4C43CH4/H5u6g61pYZg7u0HCzTAB3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FLVec/btsm4C43CH4/H5u6g61pYZg7u0HCzTAB3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFLVec%2Fbtsm4C43CH4%2FH5u6g61pYZg7u0HCzTAB3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2488&quot; height=&quot;1636&quot; data-origin-width=&quot;2488&quot; data-origin-height=&quot;1636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다운로드한&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;나무 바닥 이미지의 크기가 크기 때문에 이미지 크기를 넓이 150px, 높이 100px으로 조정하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. 프로젝트에 resources \ textures 폴더를 생성하고 다운로드한 나무 바닥 이미지 파일(wood1.jpg)을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOW1io/btsnalADRHG/LoAWWAEM2SuJQEH1lIViF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOW1io/btsnalADRHG/LoAWWAEM2SuJQEH1lIViF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOW1io/btsnalADRHG/LoAWWAEM2SuJQEH1lIViF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOW1io%2FbtsnalADRHG%2FLoAWWAEM2SuJQEH1lIViF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1648&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;2. resources \ textures&lt;span&gt; 폴더에 있는 나무 바닥 이미지 파일(wood1.jpg)을 TextureLoader(텍스처로더)로 로드합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688987678269&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 텍스처 매핑할 텍스처를 로드
const box2Texture = new THREE.TextureLoader().load('resources/textures/wood1.jpg');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 로드한 Texture(텍스처)를 Material(머티리얼)의 map 속성에 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688995525062&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 육면체에 사용할 색상과 질감의 데이터를 생성
const box2Material = new THREE.MeshStandardMaterial({map:box2Texture});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;기존에 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;color 속성은 삭제합니다. 그 이유는 Texture(텍스처)의 map color가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;color&lt;span&gt; 속성 값과 결합되어 변경되기 때문입니다. 글 끝부분에서 테스트로 color&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성과 같이 사용해 보도록 하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 두 번째 육면체 객체가 보이도록 Camera(카메라)의 위치를 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688996724479&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카메라 위치 변경
camera.position.set(0, 2, 8);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct8R1G/btsnak2VjRC/ryJIBg7oytXDsd6l3apG10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct8R1G/btsnak2VjRC/ryJIBg7oytXDsd6l3apG10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct8R1G/btsnak2VjRC/ryJIBg7oytXDsd6l3apG10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct8R1G%2Fbtsnak2VjRC%2FryJIBg7oytXDsd6l3apG10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1648&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 두 번째 육면체 객체의 모든 면이 나무 바닥 이미지로 변경됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBHpdw/btsnaTcUEvo/ArKPvFksnqeDNxysAs8Ay1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBHpdw/btsnaTcUEvo/ArKPvFksnqeDNxysAs8Ay1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBHpdw/btsnaTcUEvo/ArKPvFksnqeDNxysAs8Ay1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBHpdw%2FbtsnaTcUEvo%2FArKPvFksnqeDNxysAs8Ay1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2568&quot; height=&quot;1640&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스로 이동해서 보시면 입체적으로 확인할 수 있습니다. 나무 바닥 이미지가 육면체 객체에 잘 어울립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI4NOy/btsm5sushTI/6LVgj3UgKtkBSQUI8hJEnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI4NOy/btsm5sushTI/6LVgj3UgKtkBSQUI8hJEnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI4NOy/btsm5sushTI/6LVgj3UgKtkBSQUI8hJEnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI4NOy%2Fbtsm5sushTI%2F6LVgj3UgKtkBSQUI8hJEnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2568&quot; height=&quot;1640&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&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;이번에는 주사위 이미지를 만들어서 두 번째 육면체 객체에 Texture Mapping(텍스처 매핑)을 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주사위 이미지를 간단하게 PPT로 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀은 직사각형으로 가로/세로는 3cm, 도형 채우기는 흰색, 도형 윤곽선은 검은색입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점은 원형으로 가로/가로는 0.6cm, 도형 채우기와 도형 윤곽선은 검은색입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직사각형안의 점선은 중앙을 맞추기 위해서 사용했습니다. 저장하기 전에 점선은 삭제합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sIT9l/btsm4CKHiHo/JixYQL1UDDYOfKo9RORhGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sIT9l/btsm4CKHiHo/JixYQL1UDDYOfKo9RORhGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sIT9l/btsm4CKHiHo/JixYQL1UDDYOfKo9RORhGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsIT9l%2Fbtsm4CKHiHo%2FJixYQL1UDDYOfKo9RORhGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;102&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;180&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;각각을 PNG 파일로 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Onh3X/btsm92nzjKl/3NuzdnByansOh9UbF60KG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Onh3X/btsm92nzjKl/3NuzdnByansOh9UbF60KG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Onh3X/btsm92nzjKl/3NuzdnByansOh9UbF60KG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOnh3X%2Fbtsm92nzjKl%2F3NuzdnByansOh9UbF60KG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;139&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;245&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;그리고 프로젝트에 resources \ textures 폴더에 주사위 이미지 파일을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxrVHz/btsm8hlQCwp/Z0fIM9SS3qZn9KlCOQmLl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxrVHz/btsm8hlQCwp/Z0fIM9SS3qZn9KlCOQmLl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxrVHz/btsm8hlQCwp/Z0fIM9SS3qZn9KlCOQmLl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxrVHz%2Fbtsm8hlQCwp%2FZ0fIM9SS3qZn9KlCOQmLl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1648&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&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;여러 Texture(텍스처)들을 여러 번 로드하기 위해서 TextureLoader(텍스처로더)를 분리하여 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1689000257364&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 텍스처 로드 생성
const box2TextureLoader = new THREE.TextureLoader();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;육면체 면마다 텍스처가 다른 Material(머티리얼)을 설정해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 6개의 Material(머티리얼)을 생성해서 배열로 전달해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개의 Material(머티리얼)이 육면체에 매핑되는 순서는 오른쪽(1), 왼쪽(2), 위(3), 아래(4), 앞(5), 뒤(6)입니다. (전개도 모양은 다를 수 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xBGs1/btsnE9HOTVd/Ipv9xjdT3PzucpCNd0d681/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xBGs1/btsnE9HOTVd/Ipv9xjdT3PzucpCNd0d681/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xBGs1/btsnE9HOTVd/Ipv9xjdT3PzucpCNd0d681/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxBGs1%2FbtsnE9HOTVd%2FIpv9xjdT3PzucpCNd0d681%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot; data-origin-width=&quot;711&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;size16&quot;&gt;그러나 대부분의 주사위는 1을 기준(1이 위에 있을 경우)으로 다음과 같이 되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ndoeL/btsm9hlkq6u/UKvsAcRk60nKzWzdk0psT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ndoeL/btsm9hlkq6u/UKvsAcRk60nKzWzdk0psT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ndoeL/btsm9hlkq6u/UKvsAcRk60nKzWzdk0psT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FndoeL%2Fbtsm9hlkq6u%2FUKvsAcRk60nKzWzdk0psT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot; data-origin-width=&quot;710&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;size16&quot;&gt;그래서 6개의 Material(머티리얼)로 배열을 만들 때 주사위 위치에 맞게 텍스처(주사위 이미지)를 로드해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688998742550&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5.png&quot;)}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2.png&quot;)}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1.png&quot;)}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6.png&quot;)}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3.png&quot;)}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4.png&quot;)})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개의 면이 다른 텍스처(주사위 이미지)가 되기 위해서 Material(머티리얼) 배열로 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1689000608508&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 육면체와 색상과 질감의 데이터 배열을 결합하여 육면체 객체 생성
const box2 = new THREE.Mesh(box2Geometry, box2Materials);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds2xFS/btsm1S1uH4R/yQYkv2q59jSXzMa2EuNb21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds2xFS/btsm1S1uH4R/yQYkv2q59jSXzMa2EuNb21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds2xFS/btsm1S1uH4R/yQYkv2q59jSXzMa2EuNb21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds2xFS%2Fbtsm1S1uH4R%2FyQYkv2q59jSXzMa2EuNb21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1648&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;두 번째&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;육면체 객체의 모든 면이 주사위 이미지로 변경됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k4XUK/btsm2H6uhyC/5dy42qkVgRkPlVr4AKwpZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k4XUK/btsm2H6uhyC/5dy42qkVgRkPlVr4AKwpZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k4XUK/btsm2H6uhyC/5dy42qkVgRkPlVr4AKwpZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk4XUK%2Fbtsm2H6uhyC%2F5dy42qkVgRkPlVr4AKwpZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2568&quot; height=&quot;1640&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스로 이동해서 보시면 입체적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rnxpy/btsm8I4PKHr/WNlpakCz6sIF7Oum3K1uU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rnxpy/btsm8I4PKHr/WNlpakCz6sIF7Oum3K1uU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rnxpy/btsm8I4PKHr/WNlpakCz6sIF7Oum3K1uU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frnxpy%2Fbtsm8I4PKHr%2FWNlpakCz6sIF7Oum3K1uU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2568&quot; height=&quot;1640&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&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;2568&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b47u8u/btsm9wCz2Aa/iDnJWZmfX57nhicixbxcj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b47u8u/btsm9wCz2Aa/iDnJWZmfX57nhicixbxcj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b47u8u/btsm9wCz2Aa/iDnJWZmfX57nhicixbxcj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb47u8u%2Fbtsm9wCz2Aa%2FiDnJWZmfX57nhicixbxcj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2568&quot; height=&quot;1640&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1640&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;위에서 말한 것처럼 Material(머티리얼)의 map 속성과 color 속성이 같이 사용하게 되면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Texture(텍스처)의 map color가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;color&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성 값과 결합되어 색이 변경됩니다. map color가 흰색인 경우 color&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성 값이 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Material(머티리얼)에 &lt;/span&gt;기존 color 속성을 추가합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689054251965&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 머티리얼 배열
const box2Materials = [
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice5.png&quot;), color:box2Color}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice2.png&quot;), color:box2Color}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice1.png&quot;), color:box2Color}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice6.png&quot;), color:box2Color}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice3.png&quot;), color:box2Color}),
    new THREE.MeshStandardMaterial({map: box2TextureLoader.load(&quot;resources/textures/dice4.png&quot;), color:box2Color})
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 새로고침 할 때마다 주사위의 색상이 변경되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl6VZZ/btsnalhKChF/9rQA14izq5eBSAvww6kPkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl6VZZ/btsnalhKChF/9rQA14izq5eBSAvww6kPkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl6VZZ/btsnalhKChF/9rQA14izq5eBSAvww6kPkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl6VZZ%2FbtsnalhKChF%2F9rQA14izq5eBSAvww6kPkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2604&quot; height=&quot;1722&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;1722&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J6FhX/btsngMZha4t/e4t8WBgDggbQnEck1BGGSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J6FhX/btsngMZha4t/e4t8WBgDggbQnEck1BGGSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J6FhX/btsngMZha4t/e4t8WBgDggbQnEck1BGGSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ6FhX%2FbtsngMZha4t%2Fe4t8WBgDggbQnEck1BGGSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;77&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흰색이 아닌 다른 색상일 경우 색이 변경되어 보일 겁니다.&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>Dice</category>
      <category>Texture</category>
      <category>texture mapping</category>
      <category>TextureLoader</category>
      <category>Three.js</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/297</guid>
      <comments>https://carrotweb.tistory.com/297#entry297comment</comments>
      <pubDate>Sun, 16 Jul 2023 21:48:42 +0900</pubDate>
    </item>
    <item>
      <title>three.js 객체 결합 - Group</title>
      <link>https://carrotweb.tistory.com/296</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 만든 육면체 객체의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;와이어프레임을 선으로 그린&lt;span&gt; &lt;/span&gt;&lt;/span&gt;라인 세그먼트즈 객체를 육면체 객체와 결합해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체들을 결합하기 위해서 Group(그룹)을 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group(그룹)은 객체들을 결합시켜 하나의 객체(Object3D)를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 객체를 합치기 전에 육면체 객체(box1)와 라인 세그먼트즈 객체(box1WireframeLine)를 Scence(씬, 장면)에 추가되지 않게 주석처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688901341847&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 장면에 육면체 객체 추가
//scene.add(box1);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688901351162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 장면에 라인 세그먼트즈 객체 추가
//scene.add(box1WireframeLine);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LIODe/btsmRMsMjGk/B2FKlwkbIrvsQ9fcmNtbNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LIODe/btsmRMsMjGk/B2FKlwkbIrvsQ9fcmNtbNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LIODe/btsmRMsMjGk/B2FKlwkbIrvsQ9fcmNtbNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLIODe%2FbtsmRMsMjGk%2FB2FKlwkbIrvsQ9fcmNtbNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1644&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 라인 세그먼트즈 객체(box1WireframeLine)의 라인 색상을 붉은색에서 짙은 녹색으로 변경하고 위치(position)를 육면체 객체와 동일하게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688902507596&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 선 색상을 짙은 녹색으로 변경
box1WireframeLine.material.color = new THREE.Color(0x006400);
// 육면체 객체와 결합하기 위해 육면체 객체의 위치를 복사하여 라인 세그먼트즈 객체의 위치 설정
box1WireframeLine.position.copy(box1.position);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lVpMh/btsmQPQ0TlT/oM7kyhKywFUj3WGQdQGn4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lVpMh/btsmQPQ0TlT/oM7kyhKywFUj3WGQdQGn4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lVpMh/btsmQPQ0TlT/oM7kyhKywFUj3WGQdQGn4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlVpMh%2FbtsmQPQ0TlT%2FoM7kyhKywFUj3WGQdQGn4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1644&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&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;3. 객체들을 결합하기 위해 Group(그룹)을 생성하고 Group(그룹)에 육면체 객체(box1)와 라인 세그먼트즈 객체(box1WireframeLine)를 추가합니다. 그리고 생성된 Group(그룹)를 Scence(씬, 장면)에 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688899778777&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 육면체 객체와 라인 세그먼트즈 객체를 결합하기 위한 육면체 그룹 생성
const box1Group = new THREE.Group();
// 그룹에 육면체 객체 추가
box1Group.add(box1);
// 그룹에 라인 세그먼트즈 객체 추가
box1Group.add(box1WireframeLine);
// 장면에 육면체 그룹 추가
scene.add(box1Group);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2WnWr/btsmQQWJMyn/BfboK9Yzf4GxcbZXw0Rph1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2WnWr/btsmQQWJMyn/BfboK9Yzf4GxcbZXw0Rph1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2WnWr/btsmQQWJMyn/BfboK9Yzf4GxcbZXw0Rph1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2WnWr%2FbtsmQQWJMyn%2FBfboK9Yzf4GxcbZXw0Rph1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1644&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;육면체 객체에 와이어프레임이 결합되어 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/083yq/btsmRrB1zsq/RPKxfLPfZDkVKq75MPh9y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/083yq/btsmRrB1zsq/RPKxfLPfZDkVKq75MPh9y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/083yq/btsmRrB1zsq/RPKxfLPfZDkVKq75MPh9y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F083yq%2FbtsmRrB1zsq%2FRPKxfLPfZDkVKq75MPh9y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2438&quot; height=&quot;1622&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 라인 세그먼트즈 객체(box1WireframeLine)가 육면체 객체와 결합될 때 투명하게 결합되게 설정을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material(머티리얼)의 opacity은 0.0에서 부터 1.0까지 값을 가질 수 있습니다. opacity 값이 낮을수록 더 투명해집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688905008007&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 선이 투명해지게 설정
box1WireframeLine.material.transparent = true;
// 선의 불투명도를 낮게 설정
box1WireframeLine.material.opacity = 0.3;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1cgBI/btsmWkvu0B2/zDAFzkr2MkxuEKewGKmM40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1cgBI/btsmWkvu0B2/zDAFzkr2MkxuEKewGKmM40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1cgBI/btsmWkvu0B2/zDAFzkr2MkxuEKewGKmM40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1cgBI%2FbtsmWkvu0B2%2FzDAFzkr2MkxuEKewGKmM40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1644&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&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;2438&quot; data-origin-height=&quot;1622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNnCAv/btsmWl8WVyT/IZwwQ2rt3LWPFdZsBNTIyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNnCAv/btsmWl8WVyT/IZwwQ2rt3LWPFdZsBNTIyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNnCAv/btsmWl8WVyT/IZwwQ2rt3LWPFdZsBNTIyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNnCAv%2FbtsmWl8WVyT%2FIZwwQ2rt3LWPFdZsBNTIyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2438&quot; height=&quot;1622&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1622&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;5. 육면체 그룹 객체가 회전되게 주석처리된 육면체 객체를 육면체 그룹 객체로 변경하고 주석을 해제시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688909302149&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 애니메이팅
function animate() {
    box1Group.rotation.x += 0.01;
    box1Group.rotation.y += 0.01;
    box1Group.rotation.z += 0.01;
    controls.update();
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7EvCF/btsmQCDTb3r/wEtIA49zfjY50KZkBjJHDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7EvCF/btsmQCDTb3r/wEtIA49zfjY50KZkBjJHDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7EvCF/btsmQCDTb3r/wEtIA49zfjY50KZkBjJHDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7EvCF%2FbtsmQCDTb3r%2FwEtIA49zfjY50KZkBjJHDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1644&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&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;2438&quot; data-origin-height=&quot;1620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btCRup/btsmRoSTrTs/bnT7Vy38ziINlkPZeFi2V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btCRup/btsmRoSTrTs/bnT7Vy38ziINlkPZeFi2V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btCRup/btsmRoSTrTs/bnT7Vy38ziINlkPZeFi2V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtCRup%2FbtsmRoSTrTs%2FbnT7Vy38ziINlkPZeFi2V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2438&quot; height=&quot;1620&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1620&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;구 객체(sphere1)도 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;라인 세그먼트즈&amp;nbsp;&lt;/span&gt;객체(sphere1WireframeLine)와 결합되도록 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 구 객체(sphere1)를 Scence(씬, 장면)에 추가되지 않게 주석처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688910050059&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 장면에 구 객체 추가
//scene.add(sphere1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 라인 세그먼트즈 객체(sphere1WireframeLine)의 라인 색상을 붉은색에서 흰색으로 변경하고 위치(position)를 구 객체와 동일하게 하고 투명하게 결합되게 설정을 추가합니다. 그리고 라인 세그먼트즈 객체(sphere1WireframeLine)를 Scence(씬, 장면)에 추가되지 않게 주석처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688910058953&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 선 색상을 짙은 흰색으로 변경
sphere1WireframeLine.material.color = new THREE.Color(0xffffff);
// 선이 투명해지게 설정
sphere1WireframeLine.material.transparent = true;
// 선의 불투명도를 낮게 설정
sphere1WireframeLine.material.opacity = 0.3;
// 구 객체와 결합하기 위해 구 객체의 위치를 복사하여 라인 세그먼트즈 객체의 위치 설정
sphere1WireframeLine.position.copy(sphere1.position);
// 장면에 라인 세그먼트즈 객체 추가
//scene.add(sphere1WireframeLine);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 객체들을 결합하기 위해 Group(그룹)을 생성하고 Group(그룹)에 구 객체(sphere1)와 라인 세그먼트즈 객체(sphere1WireframeLine)를 추가합니다. 그리고 생성된 Group(그룹)를 Scence(씬, 장면)에 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688910233092&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 구 객체와 라인 세그먼트즈 객체를 결합하기 위한 구 그룹 생성
const sphere1Group = new THREE.Group();
// 그룹에 구 객체 추가
sphere1Group.add(sphere1);
// 그룹에 라인 세그먼트즈 객체 추가
sphere1Group.add(sphere1WireframeLine);
// 장면에 구 그룹 추가
scene.add(sphere1Group);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ldRaC/btsmRpqJXyG/OEkCxGzhWi0kaUFo1RRcT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ldRaC/btsmRpqJXyG/OEkCxGzhWi0kaUFo1RRcT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ldRaC/btsmRpqJXyG/OEkCxGzhWi0kaUFo1RRcT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FldRaC%2FbtsmRpqJXyG%2FOEkCxGzhWi0kaUFo1RRcT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;1644&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;1644&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;이제 three.js 사이트의 doc에 있는 예제처럼 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YLBzB/btsmWkWzGaf/MtwRIpAfVSEzQjB9VyBjG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YLBzB/btsmWkWzGaf/MtwRIpAfVSEzQjB9VyBjG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YLBzB/btsmWkWzGaf/MtwRIpAfVSEzQjB9VyBjG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYLBzB%2FbtsmWkWzGaf%2FMtwRIpAfVSEzQjB9VyBjG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2438&quot; height=&quot;1620&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1620&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;각 객체의 Segments(세그먼트, 분절 수)를 변경시켜 변화되는 모습을 보시기 바랍니다.&lt;/p&gt;</description>
      <category>Three.js/기초튼튼</category>
      <category>group</category>
      <category>Object3D</category>
      <category>opacity</category>
      <category>Three.js</category>
      <category>transparent</category>
      <author>carrotweb</author>
      <guid isPermaLink="true">https://carrotweb.tistory.com/296</guid>
      <comments>https://carrotweb.tistory.com/296#entry296comment</comments>
      <pubDate>Sun, 9 Jul 2023 22:58:12 +0900</pubDate>
    </item>
  </channel>
</rss>