<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>사이버이메지네이션</title>
    <link>https://cyberx.tistory.com/</link>
    <description>금융권 웹시스템 개발 전문회사 사이버이메지네이션의 경험과 노하우를 공유하는 기술블로그입니다</description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 07:19:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>CyberI</managingEditor>
    <image>
      <title>사이버이메지네이션</title>
      <url>https://tistory1.daumcdn.net/tistory/1871880/attach/a1698eeebb5b4db6b6c87584a81fbc04</url>
      <link>https://cyberx.tistory.com</link>
    </image>
    <item>
      <title>멀티채널 웹 컨텐츠 관리를 위한 사용이 쉽고 편리한 CMS bizXpress</title>
      <link>https://cyberx.tistory.com/322</link>
      <description>&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티채널 컨텐츠 관리에 유용한 CMS 솔루션 bizXpress의 특징을 살펴보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멀티 채널 콘텐츠 관리는 브랜드 성과와 고객 경험을 동시에 강화하는 핵심 전략으로 자리 잡고 있지만,&amp;nbsp;현실에서는 콘텐츠 생성뿐 아니라 각 채널에 최적화된 배포와 관리를 일일이 수행해야 하는 어려움이 존재합니다.&amp;nbsp;이로 인해 현업의 업무 부담이 증가하고,&amp;nbsp;작업 시간이 과도하게 소요되기도 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;bizXpress는 이러한 현실적인 문제를 효과적으로 해결해주는&amp;nbsp;CMS&amp;nbsp;솔루션입니다.&lt;br /&gt;bizXpress는 웹 콘텐츠를 손쉽게 작성&amp;middot;편집하고 다양한 채널에 배포할 수 있는 기능을 제공하여 멀티 채널 콘텐츠 관리의 복잡성을 크게 줄여줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;멀티채널 웹컨텐츠 관리에 탁월한&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;&amp;nbsp;bizXpress&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1)&amp;nbsp;&lt;/b&gt;&lt;b&gt;콘텐츠 제작과 관리의 간소화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;bizXpress는&amp;nbsp;WYSIWYG&amp;nbsp;기반의 편리한 편집 환경을 제공해,&amp;nbsp;콘텐츠 업데이트와 업로드를 보다 수월하게 처리할 수 있게 해줍니다.&amp;nbsp;이를 통해 현업 담당자는 반복적인 제작 부담 없이 콘텐츠 품질과 속도를 동시에 확보할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2)&amp;nbsp;&lt;/b&gt;&lt;b&gt;일관된 멀티 채널 배포&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;One Source&amp;nbsp;기반의 콘텐츠를&amp;nbsp;PC&amp;middot;모바일 등 여러 채널에 한번에 배포할 수 있어,&amp;nbsp;채널별로 별도 작업을 반복할 필요가 없습니다.&amp;nbsp;이를 통해 일관된 고객 경험을 유지할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3)&amp;nbsp;&lt;/b&gt;&lt;b&gt;유연한 확장성과 연계&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;bizXpress는 표준&amp;nbsp;API를 통해 다른 시스템과 손쉽게 연계할 수 있어,&amp;nbsp;기존 마케팅 솔루션&amp;middot;데이터 시스템과 통합 운영이 가능하고 확장성 있는 콘텐츠 환경을 구축할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4)&amp;nbsp;&lt;/b&gt;&lt;b&gt;발행&amp;middot;검수 과정 자동화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단순한 콘텐츠 편집을 넘어서 검수&amp;middot;승인&amp;middot;스케줄 기반 발행까지 지원해 관리 흐름을 체계화할 수 있습니다.&amp;nbsp;이를 통해 시간과 리소스를 절약하고 오류를 줄입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;5)&amp;nbsp;&lt;/b&gt;&lt;b&gt;마케팅 성과 향상 기반 제공&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;SEO&amp;nbsp;최적화 기능과 마케팅 도구와의 연계 지원으로 콘텐츠의 검색 가시성과 확산력을 높이고,&amp;nbsp;고객 반응 데이터를 수집&amp;middot;분석할 수 있어 데이터 기반 마케팅 전략 수립에 도움을 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a32c3; font-family: 'Nanum Gothic';&quot;&gt;요약하면&lt;span style=&quot;color: #3a32c3;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;멀티채널 콘텐츠 관리에서 발생하는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;반복 적이고 비효율적인 콘텐츠 제작&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;&amp;middot;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;배포 작업 /&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;채널 간 일관성 유지의 어려움 /&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;데이터 기반 성과 분석의 부재와&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;같은 어려움은&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;&amp;nbsp;bizXpress&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;를 통해 크게 개선될 수 있습니다&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;bizXpress&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;의 통합적인 콘텐츠 작성&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;&amp;middot;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;운영&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;&amp;middot;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;배포 환경은 마케터가 단순 운영 업무에서 벗어나 전략적 과업에 집중할 수 있도록 지원합니다&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://solution.cyber-i.com/solution/cms/bizxpress/index.cmd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;bizXpress 기능 및 레퍼런스 보러 가기&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://solution.cyber-i.com/main/tech/note/view/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;테크인사이트 관련 글 보러 가기&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>CyberI 제품소개/웹콘텐츠관리</category>
      <category>bizxpress</category>
      <category>CMS솔루션</category>
      <category>hybridcms</category>
      <category>WCMS</category>
      <category>멀티채널컨텐츠관리</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/322</guid>
      <comments>https://cyberx.tistory.com/322#entry322comment</comments>
      <pubDate>Wed, 4 Mar 2026 10:16:32 +0900</pubDate>
    </item>
    <item>
      <title>민첩성을 확보하는 데이터 연계 전략 MSA 환경의 메인 허브 API Gateway</title>
      <link>https://cyberx.tistory.com/321</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 D-Bridge를&amp;nbsp;API Gateway로 활용하여 서비스 디커플링을 구현하고,&amp;nbsp;개발 및 배포의 민첩성을 극대화할 수 있는 방법을 제시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;개발 조직의 독립적인 민첩성과 확장성을 향한 여정&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;, MSA (Micro Service Architecture)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;오늘날&amp;nbsp;IT&amp;nbsp;환경은 급변하며,&amp;nbsp;새로운 요구 사항에 빠르게 대응하는 민첩성(Agility)이 핵심 경쟁력입니다.&amp;nbsp;이러한 요구에 따라,&amp;nbsp;기존의 크고 복잡한 시스템(모놀리식)을 작고 독립적인 서비스 단위로 분리하는 &lt;b&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;MSA&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;Micro Service Architecture&lt;span style=&quot;color: #3a32c3;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;가 주목받고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만&amp;nbsp;MSA로의 전환은 쉽지 않습니다.&amp;nbsp;특히 기존의&amp;nbsp;&lt;b&gt;부서별/시스템별 경계&lt;/b&gt;를 넘어 수많은 마이크로 서비스 간의 복잡한 데이터 연계 및 통신 문제를 어떻게 효율적으로 관리할지 여부가 가장 큰 걸림돌입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;핵심 문제&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;: MSA&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;환경의 서비스 간 통신 복잡성&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;MSA에서는 수백 개의 서비스가 서로 통신하며 하나의 비즈니스를 완성합니다.&amp;nbsp;이 때,&amp;nbsp;서비스 간 직접적인 호출(Point-to-Point)은 다음과 같은 심각한 문제를 야기합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(1)&amp;nbsp;&lt;/b&gt;&lt;b&gt;복잡한 네트워크 그래프&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서비스가 늘어날수록 통신 경로가 기하급수적으로 늘어나 전체 아키텍처 파악 및 관리가 불가능 해집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(2)&amp;nbsp;&lt;/b&gt;&lt;b&gt;보안 및 인증 관리의 분산&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모든 서비스가 개별적으로 보안 및 인증 로직을 처리해야 하므로 일관성 확보가 어렵습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(3)&amp;nbsp;&lt;/b&gt;&lt;b&gt;서비스 간 의존성 문제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;한 서비스의&amp;nbsp;API&amp;nbsp;변경이 다른 모든 연관 서비스에 영향을 미쳐 배포와 유지보수가 어렵습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;D-Bridge는 MSA&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;구축의 핵심 기반인&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;&amp;nbsp;API Gateway&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;역할 수행&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;D-Bridge는&amp;nbsp;MSA&amp;nbsp;환경에서 마이크로 서비스들이 외부 시스템 및 다른 서비스들과 안전하고 효율적으로 상호 작용하도록 돕는&amp;nbsp;&lt;b&gt;API Gateway&lt;/b&gt;&amp;nbsp;역할을 완벽하게 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(1)&amp;nbsp;&lt;/b&gt;&lt;b&gt;서비스 디커플링(Decoupling)&amp;nbsp;및 통신 표준화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;D-Bridge API는 외부 요청과 마이크로 서비스 사이에 위치하여,&amp;nbsp;서비스의 내부 구조를 숨기고 통신을 표준화 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;&lt;b&gt;단일 접근성 제공&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모든 외부 요청은&amp;nbsp;D-Bridge API라는 단일 게이트웨이를 통해 이루어지므로,&amp;nbsp;클라이언트는 내부 서비스의 위치나 변화에 영향을 받지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;&lt;b&gt;프로토콜 변환&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마이크로 서비스가 사용하는 다양한 통신 방식(예: HTTP, TCP등)을 외부의 표준&amp;nbsp;API&amp;nbsp;프로토콜(예: RESTful)로 자동 변환하여 서비스 간의 결합도를 낮춥니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(2)&amp;nbsp;&lt;/b&gt;&lt;b&gt;중앙 집중식 보안 및 제어&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;MSA&amp;nbsp;환경에서 분산된 보안 관리는 큰 위험 요소입니다. D-Bridge는 모든 요청을 중앙에서 통제합니다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;&lt;b&gt;통합 인증/인가&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;클라이언트의 토큰 검증,&amp;nbsp;접근 권한 확인 등 보안 로직을&amp;nbsp;API Gateway에서 일괄 처리하여 각 마이크로 서비스의 개발 부담을 줄이고 보안 일관성을 확보합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;&lt;b&gt;트래픽 통제 및 모니터링&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서비스별 부하를 방지하기 위한&amp;nbsp;&lt;b&gt;유량제어&lt;/b&gt;&amp;nbsp;기능과&amp;nbsp;&lt;b&gt;모니터링&lt;/b&gt;&amp;nbsp;기능을 제공하여 안정적인 운영을 보장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(3)&amp;nbsp;&lt;/b&gt;&lt;b&gt;레거시 시스템을&amp;nbsp;MSA로 연결하는&amp;nbsp;'브릿지'&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;MSA로의 전환이 점진적으로 이루어질 때&amp;nbsp;D-Bridge의 가치는 더욱 독보입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;&lt;b&gt;하이브리드 아키텍처 지원&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;신규 마이크로 서비스와 기존의 레거시 모놀리식 시스템(DB Link&amp;nbsp;기반 등)&amp;nbsp;간의 데이터 연계를 표준화된&amp;nbsp;API&amp;nbsp;인터페이스로 통합하여,&amp;nbsp;안전하고 점진적인 시스템 전환을 가능하게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;D-Bridge&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;로 민첩한 미래 아키텍처를 설계하십시오&lt;/span&gt;&lt;span style=&quot;color: #3a32c3;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;D-Bridge는 단순히 데이터를 연결하는 것을 넘어,&amp;nbsp;복잡한&amp;nbsp;MSA&amp;nbsp;환경에서&amp;nbsp;&lt;b&gt;서비스의 독립성을 보장하고,&amp;nbsp;통신의 복잡도를 획기적으로 낮추며,&amp;nbsp;보안과 관리 효율을 극대화&lt;/b&gt;하는 핵심 인프라입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아직&amp;nbsp;MSA가 초기 단계에 있는 기관일지라도, D-Bridge를 도입하는 것은&amp;nbsp;MSA&amp;nbsp;도입 시 발생할 수 있는 데이터 연계 및 통신 문제를 미리 해결하는&amp;nbsp;&lt;b&gt;가장 효과적인 선행 투자&lt;/b&gt;가 될 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;&lt;a href=&quot;https://solution.cyber-i.com/solution/apim/dbridge/index.cmd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;D-Bridge 상세기능 보러가기&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://solution.cyber-i.com/main/tech/note/view/22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;테크인사이트 관련 글 보러가기&lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>CyberI 제품소개/API 서버</category>
      <category>API게이트웨이</category>
      <category>api솔루션</category>
      <category>D-Bridge</category>
      <category>MSA</category>
      <category>MSAAPI</category>
      <category>데이터연계</category>
      <category>마이크로서비스</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/321</guid>
      <comments>https://cyberx.tistory.com/321#entry321comment</comments>
      <pubDate>Wed, 4 Mar 2026 10:09:47 +0900</pubDate>
    </item>
    <item>
      <title>[세미나 후기] 서울지역 대학교 대상 API 솔루션 D-Bridge 발표</title>
      <link>https://cyberx.tistory.com/320</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사이버이메지네이션이 강릉 스카이베이호텔에서 10월 16일에 진행된 '2025 한국교육정보화재단 서울지역협의회 세미나'에 참여했습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기간: 2025년 10월 16일(목) ~ 17일(금)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;장소: 강릉 스카이베이호텔&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 세미나에서 사이버이메지네이션은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&amp;ldquo;Data 업무의 미래, API로 연결하다. 디지털혁신플랫폼 D-Bridge&amp;rdquo;&lt;/b&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: #006dd7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;** 발표 주요 내용&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 대학교 API 확장성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- D-Bridge 핵심 가치: 효율성, 보안성, 확장성 중심의 설계&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- SQL 기반&amp;nbsp; API 개발: D-Bridge로 API를 개발하는 동영상&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 대학교 API 활용사례: DB Link, View API 전환, 이기종 DB 데이터 연계, 교내 / 외부기관 연계&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251020_141540290_01.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1081&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IrocC/dJMb9X5pmSz/Sfyw0UNmFWPFKtCKR3lk3K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IrocC/dJMb9X5pmSz/Sfyw0UNmFWPFKtCKR3lk3K/img.jpg&quot; data-alt=&quot;data 업무의 미래 API로 연결하다 D-Bridge&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IrocC/dJMb9X5pmSz/Sfyw0UNmFWPFKtCKR3lk3K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIrocC%2FdJMb9X5pmSz%2FSfyw0UNmFWPFKtCKR3lk3K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;525&quot; data-filename=&quot;KakaoTalk_20251020_141540290_01.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1081&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;data 업무의 미래 API로 연결하다 D-Bridge&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251020_142704255_01.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yq7Ma/dJMb9eF58LM/LqtZHaUdujJszcz02PG5r1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yq7Ma/dJMb9eF58LM/LqtZHaUdujJszcz02PG5r1/img.jpg&quot; data-alt=&quot;D-Bridge 세미나 발표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yq7Ma/dJMb9eF58LM/LqtZHaUdujJszcz02PG5r1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyq7Ma%2FdJMb9eF58LM%2FLqtZHaUdujJszcz02PG5r1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;659&quot; data-filename=&quot;KakaoTalk_20251020_142704255_01.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1355&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;D-Bridge 세미나 발표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;** 사용자 인터뷰&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;D-Bridge를 도입한 대학 관계자들은 공통적으로 'Simple is Best' 라는 평가를 남겨주셨습니다.&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; font-family: 'Nanum Gothic';&quot;&gt;- 직관적인 Web UI 기반 구성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;- API 운영에 최적화된 콤팩트한 기능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;- 전문 기술인력의 유지보수 지원&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;세미나.jpg&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3fQw5/dJMb8ZCaI6v/PCJ0p1cAE7K6RyFmmWaEeK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3fQw5/dJMb8ZCaI6v/PCJ0p1cAE7K6RyFmmWaEeK/img.jpg&quot; data-alt=&quot;D-Bridge 발표현장, API 플랫폼 세미나&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3fQw5/dJMb8ZCaI6v/PCJ0p1cAE7K6RyFmmWaEeK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3fQw5%2FdJMb8ZCaI6v%2FPCJ0p1cAE7K6RyFmmWaEeK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;700&quot; data-filename=&quot;세미나.jpg&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;D-Bridge 발표현장, API 플랫폼 세미나&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; letter-spacing: 0px;&quot;&gt;서울지역 대학교 관계자분들께서 높은 관심을 보여주셨고, 발표에 집중해주셔서 감사드립니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; letter-spacing: 0px;&quot;&gt;앞으로도 교육기관의 디지털 혁신을 위한 API 솔루션을 지속적으로 발전시켜 나가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CyberI 제품소개/API 서버</category>
      <category>2025서울지역협의회세미나</category>
      <category>2025한국교육정보화재단세미나</category>
      <category>D-Bridge솔루션</category>
      <category>DB링크대체API솔루션</category>
      <category>SQL기반API개발</category>
      <category>글로컬대학API</category>
      <category>대학API확장성</category>
      <category>대학교API솔루션</category>
      <category>산학협력API</category>
      <category>상위대학선택API솔루션</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/320</guid>
      <comments>https://cyberx.tistory.com/320#entry320comment</comments>
      <pubDate>Mon, 20 Oct 2025 15:16:36 +0900</pubDate>
    </item>
    <item>
      <title>보안 위협과 대응(1편) - 랜섬웨어</title>
      <link>https://cyberx.tistory.com/319</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최근 가장 심각한 보안 위협으로 떠오른 랜섬웨어. 이에 대해 알아본 후 웹사이트 운영 업무를 하면서 직접 경험했던 리눅스 서버의 로그와 톰캣(Tomcat) 로그를 통해 이상 징후를 확인하고 대응했던 경험을 공유하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;1. 랜섬웨어란 무엇인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 최근 랜섬웨어 공격 동향과 피해 사례&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 랜섬웨어 초기 흔적 파악하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-1. 로그(톰캣, 아파치) 분석&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-2. 비정상 접속 탐지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-3. 모니터링을 통한 탐지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 파일 변조 탐지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4-1. find, lsattr, inotify 활용 실시간 탐지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4-2. 파일 무결성 점검 및 관리&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. &lt;span style=&quot;color: #0000ff;&quot;&gt;랜섬웨어란 &lt;/span&gt;무엇인가?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #ff0000;&quot;&gt;Ransom&lt;/span&gt;(몸값) + Soft&lt;span style=&quot;color: #ff0000;&quot;&gt;ware&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname01.bmp&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6XIAS/btsQWOBtTzw/kl3uhaHc525RX2v2zejqbK/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6XIAS/btsQWOBtTzw/kl3uhaHc525RX2v2zejqbK/img.bmp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6XIAS/btsQWOBtTzw/kl3uhaHc525RX2v2zejqbK/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6XIAS%2FbtsQWOBtTzw%2Fkl3uhaHc525RX2v2zejqbK%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;374&quot; height=&quot;310&quot; data-filename=&quot;noname01.bmp&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;* 일부 파일의 손상&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* 시스템 파일의 비정상적인 변조&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* 숨겨진 추가 악성코드&lt;/span&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 최근 랜섬웨어 공격 동향과 피해 사례&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 이중 갈취&lt;/span&gt;(데이터 암호화 + 정보 유출 협박) : 데이터 암호화 후 금전 요구 방식에서 데이터 암호화와 더불어 DDos 공격을 병행하고&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;정보 유출 협박까지 하는 이중 갈취의 형태가 많이 나타나고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 공급망 공격&lt;/span&gt; : 제3자 도구나 서비스를 이용하여 표적의 시스템 또는 네트워크에 침투하는 간접 공격입니다. 전자상거래 사이트에서 공통으로 사용하는 모듈의 파일을 변조한 후 이 모듈을 통해 전자상거래 사이트들을 공격하는 방법이라고 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 클라우드 환경 공격&lt;/span&gt; : 클라우드 환경이 증가함에 따라 클라우드 서버 자체의 취약점을 이용하거나 사용자의 설정이나 관리 부실을 악용하여&lt;span style=&quot;color: #0000ff;&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&amp;lt;최근 발생한 국내 사례&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* YES24 서비스 마비 : 25년 6월 서버의 주요 데이터를 암호화하고, 이를 복구 해주는 대가로 금전을 요구한 이중 갈취 형태&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* SGI서울보증 서비스 마비 : 25년 7월 SSL-VPN 장비의 취약점을 이용하여 로그인 시도 횟수 제안의 미흡함을 무차별 대입 공역으로 침투하여 공격 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* 롯데카드 개인정보 유출 : 결제 시스템의 보안 취약점을 이용해 침투&lt;/span&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이밖에 알려지지 않은 많은 기업이 지금 이 시각에도 랜섬웨어 공격을 당하고 있으며 아래의 자료와 같이 랜섬웨어 발생 건수는 나날이 증가하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname02.bmp&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/batJYo/btsQYLpUWci/Pg4Z4N1PGw4V0Ra4HhZBEk/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/batJYo/btsQYLpUWci/Pg4Z4N1PGw4V0Ra4HhZBEk/img.bmp&quot; data-alt=&quot;[출처 : GuidePoint Security]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/batJYo/btsQYLpUWci/Pg4Z4N1PGw4V0Ra4HhZBEk/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbatJYo%2FbtsQYLpUWci%2FPg4Z4N1PGw4V0Ra4HhZBEk%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;183&quot; data-filename=&quot;noname02.bmp&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[출처 : GuidePoint Security]&lt;/figcaption&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 랜섬웨어 초기 흔적 파악하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname03.bmp&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vEanM/btsQXRRHld8/bAajRxa2v69uo62n8iUa60/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vEanM/btsQXRRHld8/bAajRxa2v69uo62n8iUa60/img.bmp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vEanM/btsQXRRHld8/bAajRxa2v69uo62n8iUa60/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvEanM%2FbtsQXRRHld8%2FbAajRxa2v69uo62n8iUa60%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;269&quot; height=&quot;269&quot; data-filename=&quot;noname03.bmp&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 비정상 파일 변경 및 암호화 내역 &lt;/span&gt;: 서버 내 중요 파일들이 갑자기 변경되거나 임의의 문자열로 변조된 확장자를 갖게 함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 예기치 않은 &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;CPU &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;및 디스크 사용률&lt;/span&gt; &lt;span style=&quot;color: #0000ff;&quot;&gt;증가&lt;/span&gt; : 암호화 작업 등으로 인해 서버 리소스 사용량이 급격히 상승&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 네트워크 트래픽 비정상 증대&lt;/span&gt; : 외부 공격자와 통신하거나, 내부에서 대규모 데이터 전송 시도 시 트래픽 급증&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;로그에서 반복적인 인증 실패 및 계정 이상 활동&lt;/span&gt; : 접속 계정 탈취나 비정상 로그인 시도가 빈번히 기록됨&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 비정상적인 접근 시도&lt;/span&gt; : 특정 IP가 짧은 시간 동안 대량의 접속 시도를 하거나 보안 취약점이나 SQL Injection 등을 찾으려는 비정상적인&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;요청을 보냄&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* 알 수 없는 스크립트나 프로세스 실행 시도&lt;/span&gt; : 평소 실행하지 않던 스크립트, 특히 PowerShell, Bash, Python 등 의심스러운 스크립트 실행 흔적이 있거나 비정상적으로 높은 리소스 사용을 일으키는 낯선 프로세스 발견&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3-1. 로그(톰캣, 아파치) 분석&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;랜섬웨어는 시스템에 침투하고 암호화 작업을 실행하는 과정에서 흔적을 남기며, 모두 로그(Log)에 남게 됩니다.&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;톰캣 환경에서는 접속 로그, 에러 로그, 애플리케이션 로그를 함께 분석함으로써 비정상 요청을 조기에 식별할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* catalina.out&lt;/span&gt; : 서버의 실행 및 종료기록과 애플리케이션의 코드 내 콘솔 출력 등&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;예기치 않은 스택 트레이스나 특정 라이브러리 로딩 오류 정보가 기록됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;* localhost_access_log.txt&lt;/span&gt; : 클라이언트 IP, 요청 URL, 응답 코드와 서비스 접근 로그가 기록되어 공격자의 취약점 탐색, 무차별 대입 공격(Brute-force attack), SQL 인젝션 공격 시도 등이 기록될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* manager 또는 host-manager 로그 : 톰캣 관리 콘솔에 대한 로그인 접속을 기록합니다. 랜섬웨어는&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;관리자 권한을 탈취하여 시스템에 침투하는 경우가 많아 외부의 악의적인 접근 시도를 탐지하기 위해서 중요한 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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-filename=&quot;noname04.bmp&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWcsJs/btsQXF42kle/sYEzaiackKVvdeeW2bjpsK/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWcsJs/btsQXF42kle/sYEzaiackKVvdeeW2bjpsK/img.bmp&quot; data-alt=&quot;(톰캣 logs 폴더에서 확인 할 수 있는 로그 파일들)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWcsJs/btsQXF42kle/sYEzaiackKVvdeeW2bjpsK/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWcsJs%2FbtsQXF42kle%2FsYEzaiackKVvdeeW2bjpsK%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;238&quot; data-filename=&quot;noname04.bmp&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(톰캣 logs 폴더에서 확인 할 수 있는 로그 파일들)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3-2. 비정상 접속 탐지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;1) &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;비정상적인 접근 및 업로드 요청&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;/admin, /phpmyadmin 등 관리자 페이지에 대한 무차별 대입 공격 흔적 또는 /shell.jsp와 같이 존재하지 않는 웹 쉘 업로드 흔적 탐색&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname05.bmp&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBi4Ng/btsQVVHLdEO/ETEWgx9ie7oFCAlY4cKFs1/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBi4Ng/btsQVVHLdEO/ETEWgx9ie7oFCAlY4cKFs1/img.bmp&quot; data-alt=&quot;(비정상 접속 탐지 명령어와 탐지 내역)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBi4Ng/btsQVVHLdEO/ETEWgx9ie7oFCAlY4cKFs1/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBi4Ng%2FbtsQVVHLdEO%2FETEWgx9ie7oFCAlY4cKFs1%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;94&quot; data-filename=&quot;noname05.bmp&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(비정상 접속 탐지 명령어와 탐지 내역)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;2) &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;비정상 파라미터&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;%00, %2e, %2f 등 비정상적인 파라미터 흔적 탐색&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname06.bmp&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4uad8/btsQYeeN4pk/VbjLD6B53J4fliCLGQa9Xk/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4uad8/btsQYeeN4pk/VbjLD6B53J4fliCLGQa9Xk/img.bmp&quot; data-alt=&quot;(비정상 파라미터 탐지 명령어와 내역)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4uad8/btsQYeeN4pk/VbjLD6B53J4fliCLGQa9Xk/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4uad8%2FbtsQYeeN4pk%2FVbjLD6B53J4fliCLGQa9Xk%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;121&quot; data-filename=&quot;noname06.bmp&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(비정상 파라미터 탐지 명령어와 내역)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;3) &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;이상 응답 코드 비율 확인&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;공격자가 취약점을 찾기 위해 시도 할 경우 404(Not Found)나 403(Forbidden) 응답이 갑자기 증가할 수 있으므로 응답 코드 비율 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname07.bmp&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzt5jh/btsQW9eiNtF/pAgUumToxrEK46FJ40kEE1/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzt5jh/btsQW9eiNtF/pAgUumToxrEK46FJ40kEE1/img.bmp&quot; data-alt=&quot;(응답 코드 비율 확인 _ 로그 출력 형태에 맞게 명령어 작성)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzt5jh/btsQW9eiNtF/pAgUumToxrEK46FJ40kEE1/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdzt5jh%2FbtsQW9eiNtF%2FpAgUumToxrEK46FJ40kEE1%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;190&quot; data-filename=&quot;noname07.bmp&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(응답 코드 비율 확인 _ 로그 출력 형태에 맞게 명령어 작성)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;4) SQL &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;인젝션이나 스크립트 삽입 흔적 탐색&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;union, select, OR 1=1, &amp;lt;script&amp;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname08.bmp&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b42IrU/btsQXG3Zv28/rue3XkS1MCbwJrpESus270/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b42IrU/btsQXG3Zv28/rue3XkS1MCbwJrpESus270/img.bmp&quot; data-alt=&quot;(SQL 인젝션 및 스크립트 삽입 흔적 탐색과 내역)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b42IrU/btsQXG3Zv28/rue3XkS1MCbwJrpESus270/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb42IrU%2FbtsQXG3Zv28%2Frue3XkS1MCbwJrpESus270%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;92&quot; data-filename=&quot;noname08.bmp&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(SQL 인젝션 및 스크립트 삽입 흔적 탐색과 내역)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;5) &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;에러 및 예외 발생 내역 확인&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;catalina.out의 다양한 에러와 예외 발생 내역 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname09.bmp&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tusmO/btsQVRL93QJ/edIifZM3VjpPhDSiHqYU3K/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tusmO/btsQVRL93QJ/edIifZM3VjpPhDSiHqYU3K/img.bmp&quot; data-alt=&quot;(catalina.out에서 검출되는 다양한 에러와 예외)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tusmO/btsQVRL93QJ/edIifZM3VjpPhDSiHqYU3K/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtusmO%2FbtsQVRL93QJ%2FedIifZM3VjpPhDSiHqYU3K%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;158&quot; data-filename=&quot;noname09.bmp&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(catalina.out에서 검출되는 다양한 에러와 예외)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-3. 모니터링을 통한 탐지&lt;/span&gt;&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;시각적으로 한눈에 서버의 상태를 확인할 수 있는 모니터링 서비스&lt;/span&gt;는 각종 위협에 빠르게 대응할 수 있기 때문에 &lt;span style=&quot;color: #0000ff;&quot;&gt;서비스 운영자가 갖추어야 할 필수 요소&lt;/span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;아래의 화면은 &lt;span style=&quot;color: #0000ff;&quot;&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-filename=&quot;noname10.bmp&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNzomQ/btsQWr0NTpQ/0DPi019dS4oOejDm4izryK/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNzomQ/btsQWr0NTpQ/0DPi019dS4oOejDm4izryK/img.bmp&quot; data-alt=&quot;(모니터링 대시보드의 전체적인 모습)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNzomQ/btsQWr0NTpQ/0DPi019dS4oOejDm4izryK/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNzomQ%2FbtsQWr0NTpQ%2F0DPi019dS4oOejDm4izryK%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;311&quot; data-filename=&quot;noname10.bmp&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(모니터링 대시보드의 전체적인 모습)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 서버 자원 현황&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨테이너(Tomcat)별 메모리 사용률과 초당 트랜잭션(TPS:Transaction per second) 개수를 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;첨부1.Server Resource.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5bQq/btsQVHbXLAq/ZzKxJS8v6gu9y7kjdEI2H0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5bQq/btsQVHbXLAq/ZzKxJS8v6gu9y7kjdEI2H0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5bQq/btsQVHbXLAq/ZzKxJS8v6gu9y7kjdEI2H0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/k5bQq/btsQVHbXLAq/ZzKxJS8v6gu9y7kjdEI2H0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;235&quot; data-filename=&quot;첨부1.Server Resource.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) Active Service&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;250ms 간격으로 컨테이너와 was 단위별로 수행되는 서비스 개수를 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;첨부2.Active Service.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TF7Bf/btsQXMJRrNT/8wDke8h8udI7RFOoZgks2K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TF7Bf/btsQXMJRrNT/8wDke8h8udI7RFOoZgks2K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TF7Bf/btsQXMJRrNT/8wDke8h8udI7RFOoZgks2K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/TF7Bf/btsQXMJRrNT/8wDke8h8udI7RFOoZgks2K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;466&quot; data-filename=&quot;첨부2.Active Service.gif&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3) XLOG&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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-filename=&quot;첨부3.XLOG.gif&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed2NDw/btsQWPG9nmT/Qwg9l5T1K9bXve5ys8Lfq1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed2NDw/btsQWPG9nmT/Qwg9l5T1K9bXve5ys8Lfq1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed2NDw/btsQWPG9nmT/Qwg9l5T1K9bXve5ys8Lfq1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ed2NDw/btsQWPG9nmT/Qwg9l5T1K9bXve5ys8Lfq1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;450&quot; data-filename=&quot;첨부3.XLOG.gif&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 밖에도 &lt;span style=&quot;color: #0000ff;&quot;&gt;서버 스토리지 용량&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;비밀번호 변경 만료일&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;인증서 만료일&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;차단 &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;IP &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;목록&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;사이트별 접속 수 상위 &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;IP &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;목록&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;등 &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;서버 운영에 필요한 다양한 정보를 한눈에 알아볼 수 있게&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #0000ff;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. 서버 내 파일 변조 탐지하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;대표적인 3가지 명령어를 통해서 파일의 상태를 확인하는 방법에 대해 알아보겠습니다.&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4-1. find, lsattr, inotify 활용 실시간 탐지&lt;/span&gt;&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;font-family: 'Nanum Gothic';&quot;&gt;랜섬웨어가 서버 내부의 중요 파일을 암호화하거나 변조할 때는 파일 생성&amp;middot;수정&amp;middot;삭제 이벤트가 반드시 발생합니다. 따라서 파일 변화를 실시간으로 추적하거나 정기적으로 검사해야 합니다.&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;font-family: 'Nanum Gothic';&quot;&gt;1) find : 변경 파일 탐지&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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-filename=&quot;noname14.bmp&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BFVqU/btsQVzdYmwe/vP9KkWh0lgdFUz6erpYDpk/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BFVqU/btsQVzdYmwe/vP9KkWh0lgdFUz6erpYDpk/img.bmp&quot; data-alt=&quot;(1일 내 변경된 파일 목록)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BFVqU/btsQVzdYmwe/vP9KkWh0lgdFUz6erpYDpk/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBFVqU%2FbtsQVzdYmwe%2FvP9KkWh0lgdFUz6erpYDpk%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;444&quot; height=&quot;132&quot; data-filename=&quot;noname14.bmp&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(1일 내 변경된 파일 목록)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) lsattr : 파일 속성 비교&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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-filename=&quot;noname15.bmp&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O7lhE/btsQWyZGtwt/UxfHyy6GNhdYfLT9SSVQyK/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O7lhE/btsQWyZGtwt/UxfHyy6GNhdYfLT9SSVQyK/img.bmp&quot; data-alt=&quot;(catalina.out 파일의 특수 속성 확인)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O7lhE/btsQWyZGtwt/UxfHyy6GNhdYfLT9SSVQyK/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO7lhE%2FbtsQWyZGtwt%2FUxfHyy6GNhdYfLT9SSVQyK%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;39&quot; data-filename=&quot;noname15.bmp&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(catalina.out 파일의 특수 속성 확인)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname16.bmp&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gwfsg/btsQVtdQ99K/uOxstmecXAxEMkrkX9IDPK/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gwfsg/btsQVtdQ99K/uOxstmecXAxEMkrkX9IDPK/img.bmp&quot; data-alt=&quot;(변경 불가 속성이 적용되어 있을 때)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gwfsg/btsQVtdQ99K/uOxstmecXAxEMkrkX9IDPK/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGwfsg%2FbtsQVtdQ99K%2FuOxstmecXAxEMkrkX9IDPK%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;39&quot; data-filename=&quot;noname16.bmp&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(변경 불가 속성이 적용되어 있을 때)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3) inotify : 실시간 파일 변경 이벤트 감시&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파일 시스템의 변화를 실시간으로 감지. &lt;u&gt;inotify-tools &lt;/u&gt;&lt;u&gt;패키지&lt;/u&gt;를 사용하며 탐지 이벤트는 아래와 같음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_ACCESS: 파일이 읽힐 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_MODIFY: 파일이 수정될 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_ATTRIB: 파일 속성(권한 등)이 바뀔 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_CLOSE_WRITE: 파일 작성 완료 후 닫힐 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_CREATE: 파일/폴더가 새로 생성될 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_DELETE: 파일/폴더가 삭제될 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_OPEN: 파일이 열릴 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * IN_MOVED_FROM, IN_MOVED_TO: 파일/폴더가 이동될 때&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname17.bmp&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0eAz8/btsQYGoBnE0/y5Q6DLHIJF0LdrkLghWo7K/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0eAz8/btsQYGoBnE0/y5Q6DLHIJF0LdrkLghWo7K/img.bmp&quot; data-alt=&quot;(inotify-tools 패키지 설치 필요)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0eAz8/btsQYGoBnE0/y5Q6DLHIJF0LdrkLghWo7K/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0eAz8%2FbtsQYGoBnE0%2Fy5Q6DLHIJF0LdrkLghWo7K%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;48&quot; data-filename=&quot;noname17.bmp&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(inotify-tools 패키지 설치 필요)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname18.bmp&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eEd3wP/btsQVyzj0S3/rUE0K7Sz74KYleJaRh6bm0/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eEd3wP/btsQVyzj0S3/rUE0K7Sz74KYleJaRh6bm0/img.bmp&quot; data-alt=&quot;(inotifywait 사용 예시 _ 출처 : https://linuxtld.com/install-inotifywait/)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eEd3wP/btsQVyzj0S3/rUE0K7Sz74KYleJaRh6bm0/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeEd3wP%2FbtsQVyzj0S3%2FrUE0K7Sz74KYleJaRh6bm0%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;238&quot; data-filename=&quot;noname18.bmp&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(inotifywait 사용 예시 _ 출처 : https://linuxtld.com/install-inotifywait/)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4-2. 파일 무결성 점검 및 관리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;1) 해시(hash) 기반 무결성 검사&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;대표적으로 MD5, SHA-1, SHA-256 해시 함수가 사용되며 파일 내용이 바뀌면 해시값이 달라지므로 이전에 저장한 해시값과 비교해 변경 여부를 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname19.bmp&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w8ngN/btsQWP1qk0r/VrtuJhP11SEUqmEUhqNQs1/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w8ngN/btsQWP1qk0r/VrtuJhP11SEUqmEUhqNQs1/img.bmp&quot; data-alt=&quot;(해시값 생성 &amp;amp;amp; 확인 &amp;amp;amp; 검증)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w8ngN/btsQWP1qk0r/VrtuJhP11SEUqmEUhqNQs1/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw8ngN%2FbtsQWP1qk0r%2FVrtuJhP11SEUqmEUhqNQs1%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;92&quot; data-filename=&quot;noname19.bmp&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(해시값 생성 &amp;amp; 확인 &amp;amp; 검증)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;noname20.bmp&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;59&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yv0tM/btsQWvPrTIu/05wgtYZkGCmey5JPEiDKq1/img.bmp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yv0tM/btsQWvPrTIu/05wgtYZkGCmey5JPEiDKq1/img.bmp&quot; data-alt=&quot;(파일 내용 수정 후 해시값 검증)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yv0tM/btsQWvPrTIu/05wgtYZkGCmey5JPEiDKq1/img.bmp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyv0tM%2FbtsQWvPrTIu%2F05wgtYZkGCmey5JPEiDKq1%2Fimg.bmp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;59&quot; data-filename=&quot;noname20.bmp&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;59&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(파일 내용 수정 후 해시값 검증)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) 파일 무결성 모니터링 도구 도입&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; &amp;nbsp; AIDE, Tripwire 같은 파일 무결성을 점검하는 프로그램을 이용&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; &amp;nbsp;시스템 내 주요 파일과 디렉토리의 해시값을 데이터베이스로 저장 후 주기적으로 변조 여부 탐지&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;font-family: 'Nanum Gothic';&quot;&gt;3) 운영 권장 사항&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * 정상 파일의 해시값은 별도의 안전한 장소에 저장해 관리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * 무결성 점검 작업을 자동화하여 주기적으로 시행&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; * 해시값을 보관하는 데이터베이스나 무결성 점검 스크립트에 대한 접근 권한 관리 철저&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이상으로 &lt;span style=&quot;color: #0000ff;&quot;&gt;랜섬웨어의 개념부터 최근 공격 동향&lt;/span&gt;, 그리고 &lt;span style=&quot;color: #0000ff;&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;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;이를 통해 이론적 지식이 실제 상황에서 어떻게 적용되는지, 그리고 현업 운영자가 체감하는 보안 대응 과정은 어떤지 생생하게 전해드리겠습니다.&lt;/span&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;다음 편 예고: 보안 위협과 대응(2편) &amp;ndash; 실제 공격 사례와 대응 경험&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;</description>
      <category>유용한 정보</category>
      <category>랜섬웨어 #</category>
      <category>랜섬웨어사례</category>
      <category>랜섬웨어초기징후</category>
      <category>랜섬웨어흔적</category>
      <category>로그분석</category>
      <category>모니터링솔루션</category>
      <category>모니터링탐지</category>
      <category>무결성점검</category>
      <category>사이버이메지네이션모니터링</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/319</guid>
      <comments>https://cyberx.tistory.com/319#entry319comment</comments>
      <pubDate>Wed, 1 Oct 2025 16:58:26 +0900</pubDate>
    </item>
    <item>
      <title>[React] React + Upbit API로 실시간 코인 시세 사이트 만들기 (2)</title>
      <link>https://cyberx.tistory.com/318</link>
      <description>&lt;p id=&quot;2176f578-3fe1-8072-a1de-e248f377075f&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-8088-8c17-c79a0eb06f9f&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 앞선 포스팅에서 구현한 API와 WebSocket 연동이후에 데이터를 받아 화면에 보여주는 부분을 구현해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-805d-9df8-eb3496d25bee&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-80d9-87ea-f90a2bb24604&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;목차&lt;/span&gt;&lt;/p&gt;
&lt;ol id=&quot;2176f578-3fe1-8049-9b68-c02a251b0734&quot; style=&quot;list-style-type: decimal; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;데이터 가공과 UI 수정&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;검색 및 탭기능 구현&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;2176f578-3fe1-80a3-9202-cd8a03855923&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;데이터 가공과 UI 수정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;2156f578-3fe1-805b-b1be-c210278fdee7&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ProductTable.tsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-80ce-84f1-e704129e597c&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실시간 데이터를 화면에 보여주기 위해 ProductTable 컴포넌트를 구현해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8057-9815-d5fc9f0e6f73&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ProductTable은 현재 선택된 마켓 탭과 검색 조건에 따라 화면에 보여줄 코인 목록(visible)과 실시간 시세 데이터를 테이블 형식으로 렌더링하는 컴포넌트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-80f4-8bc3-f814ea3c3622&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ProductTable내부에서는 Zustand의 useUpbitStore을 사용하여 visible 배열상태로 구독하고, 해당 배열을 순회하며 ProductItem컴포넌트를 동적으로 생성하도록 구현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-80c5-a70f-e3a232f60546&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;visible배열에 변화가생기면 ProductTable 컴포넌트는 리렌더링이 되고, 이로인해 ProductItem 컴포넌트도 다시 렌더링됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-808e-80a6-c86b9c3ff231&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ProductItem 컴포넌트에는 props를 사용하여 code값을 전달합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8073-9842-e7d9c713018f&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React.memo를 사용하여 불필요한 렌더링을 방지하도록 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752130104647&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import './ProductTable.css';
import React from 'react';
import { useUpbitStore } from '@/stores/upbitStore';
import ProductItem from '@/components/upbit/ProductItem';

const ProductTable = () =&amp;gt; {
  const visible = useUpbitStore((state) =&amp;gt; state.visible);

  return (
    &amp;lt;div className=&quot;contentsArea&quot;&amp;gt;
      &amp;lt;table className=&quot;checkTable&quot;&amp;gt;
        &amp;lt;colgroup&amp;gt;
          &amp;lt;col width=&quot;140px&quot; /&amp;gt;
          &amp;lt;col width=&quot;110px&quot; /&amp;gt;
          &amp;lt;col width=&quot;90px&quot; /&amp;gt;
        &amp;lt;/colgroup&amp;gt;
        &amp;lt;thead&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;한글명&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;현재가&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;전일대비&amp;lt;/th&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
          {visible.map((product) =&amp;gt; (
            &amp;lt;ProductItem key={product.code} code={product.code} /&amp;gt;
          ))}
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default React.memo(ProductTable);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-80fd-ac77-eb043961f084&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Props란&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8047-9702-da68ca01dec3&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식&lt;/span&gt;&lt;/p&gt;
&lt;ul id=&quot;2156f578-3fe1-80cc-84ed-d0401a1b1394&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단방향 데이터 흐름(부모 컴포넌트 &amp;gt; 자식 컴포넌트)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2156f578-3fe1-80fa-b38b-c231cda43867&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자식 컴포넌트에서는 props 수정 불가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2156f578-3fe1-800a-b834-efe96eda3164&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;props의 타입 지정가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752130127067&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모 컴포넌트
const Parent = () =&amp;gt; {
  return &amp;lt;Child name=&quot;Alice&quot; age={25} /&amp;gt;;
};

// 자식 컴포넌트
const Child = ({ name, age }: { name: string; age: number }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;이름: {name}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;나이: {age}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8026-b9e6-e9d55b5d3ad5&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음 예제와 같이 부모컴포넌트에서 자식컴포넌트로 데이터를 전달할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8001-8e51-d494bff72dfd&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자식컴포넌트는 props를 함수의 매개변수처럼 받아서 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8031-ae7d-fe187ff9c56a&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8028-b15f-f69c4da2e0e8&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;State란&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴포넌트의 내부에서 생성되고 관리되는 동적인데이터입니다. 이 값이 변경되면 컴포넌트가 자동으로 렌더링됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;ul id=&quot;2166f578-3fe1-80ca-9a64-e61069ac8475&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;useState를 사용해 상태변수 선언 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2156f578-3fe1-805d-98f4-cc6b31bdd4a7&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;setState를 통해 변경 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2156f578-3fe1-80d8-8a85-c81e375dde67&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴포넌트 자체에서 생성하고, 관리함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2156f578-3fe1-80c7-951a-d48c53ad9039&quot; style=&quot;list-style-type: disc; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;값이 변경되면 컴포넌트가 자동으로 리렌더링됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752130149096&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react';

const Counter = () =&amp;gt; {
  const [count, setCount] = useState(0); // count라는 state 변수 선언

  const handleClick = () =&amp;gt; {
    setCount(count + 1); // state 변경 &amp;rarr; 리렌더링 발생
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;현재 카운트: {count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;+1 증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-80ec-9000-e1c795af85e6&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음 예제와 같이 useState를 사용해 상태변수를 선언하고, setCount를 통해 상태를 변경할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-8094-b241-fef6101470c4&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-80fc-b37a-c90194d28cfd&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;ProductItem.tsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-80de-a81e-c7812abe1a13&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;개별 코인의 정보를 표시하기위해 ProductItem 컴포넌트를 구현해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-808e-b05d-d654d2ee5108&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ProductItem는 한개 코인의 정보를 표시하는 단일 행 컴포넌트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-804e-aea2-d116ca4b2723&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ProductTable컴포넌트에서 props로 전달받은 code(종목코드) 값을 기반으로 해당 코인의 실시간 정보만 상태에서 선택적으로 구독합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2156f578-3fe1-800d-9b48-da5437409912&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;UseEffect를 사용하여 가격이 변화되었는지 감지하고, 가격이 변화되었을 경우 UI에 하이라이트 효과(빨간색/파란색 테두리)를 잠시 보여주도록 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-80be-a696-e42aebce6835&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ProductTable과 동일하게 React.memo를 사용하여 불필요한 렌더링을 방지하도록 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1752130173281&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import './ProductItem.css';
import React from 'react';
import { useEffect, useRef, useState } from 'react';
import { useUpbitStore } from '@/stores/upbitStore';
import { formatPriceByTab } from '@/utils/formatPrice';

interface Props {
  code: string;
}

const ProductItem = ({ code }: Props) =&amp;gt; {
  //선택적 구독
  const ticker = useUpbitStore((state) =&amp;gt; state.tickers[code]);
  const prev = useUpbitStore((state) =&amp;gt; state.prevPrices[code]);
  const tab = useUpbitStore((state) =&amp;gt; state.tab);
  const product = useUpbitStore((state) =&amp;gt;
    state.products[tab].find((p) =&amp;gt; p.code === code),
  );

  const [highlight, setHighlight] = useState&amp;lt;'rise' | 'fall' | null&amp;gt;(null);
  const timeoutRef = useRef&amp;lt;NodeJS.Timeout | null&amp;gt;(null);

  const changeRate = ticker?.signed_change_rate;
  const tradePrice = ticker?.trade_price;
  const change = ticker?.change; // &quot;RISE&quot; | &quot;FALL&quot; | &quot;EVEN&quot;

  const formattedPrice = formatPriceByTab(tradePrice, tab);

  const formattedRate =
    typeof changeRate === 'number' ? `${(changeRate * 100).toFixed(2)}` : '-';

  const textColorClass =
    change === 'RISE' ? 'text-danger' : change === 'FALL' ? 'text-primary' : '';

  // 가격 변화 감지
  useEffect(() =&amp;gt; {
    if (!prev || !tradePrice || prev === tradePrice) return;

    if (tradePrice &amp;gt; prev) setHighlight('rise');
    else setHighlight('fall');

    // 200ms 동안 테두리 표시용
    if (timeoutRef.current) clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() =&amp;gt; {
      setHighlight(null);
    }, 200);
  }, [tradePrice, prev]);

  if (!product) return null;

  return (
    &amp;lt;tr&amp;gt;
      &amp;lt;th&amp;gt;{product.korean_name}&amp;lt;/th&amp;gt;
      &amp;lt;td className={textColorClass}&amp;gt;
        &amp;lt;div
          className={`alignRight ${highlight === 'rise' ? 'highlight-red' : highlight === 'fall' ? 'highlight-blue' : ''}`}
        &amp;gt;
          {formattedPrice}
        &amp;lt;/div&amp;gt;
      &amp;lt;/td&amp;gt;
      &amp;lt;td className={`alignRight  ${textColorClass}`}&amp;gt;{formattedRate}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  );
};

export default React.memo(ProductItem);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-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: #37352f; text-align: start; font-family: 'Nanum Gothic';&quot;&gt; 마켓정보에 따라 가격 포맷팅을 다르게 설정합니다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752130195306&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 *  탭별 가격 포맷팅
 * - KRW: 쉼표 (toLocaleString)
 * - BTC: 소수점 8자리
 * - USDT: 소수점 3자리
 */
import type { MarketTab } from '@/stores/upbitStore'

export const formatPriceByTab = (price: number, tab: MarketTab): string =&amp;gt; {
    if (typeof price !== 'number') return '0.00'
  
    if (price &amp;gt; 1) {
      return price.toLocaleString('en')
    }
  
    let fixed = 2
    if (tab === 'BTC') fixed = 8
    else if (tab === 'USDT') fixed = 3
  
    return price.toFixed(fixed)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-80c9-b1e8-fd17296214d3&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 해서 코인목록과 실시간 시세를 보여줄 테이블영역의 구현이 완료되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-8094-accf-f63220806583&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 마켓을 선택할 수 있는 탭 영역과 찾고자 하는 코인들을 검색할 수 있는 검색영역을 구현해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-80f8-bf6e-c6e3e6357d35&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2176f578-3fe1-804e-b400-c17186decc6c&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;검색 및 탭기능 구현&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;2166f578-3fe1-8046-91c2-f3524f595b65&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;TabSelector.tsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-8085-bde8-eff6decf93d5&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자가 코인 마켓 탭 (KWR, BTC, USDT) 을 선택할 수 있도록 제공하는 컴포넌트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-8004-aea5-c2d71087f52e&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자가 특정 탭을 클릭하면 전역상태의 탭 정보를 업데이트하고, 이를 감지한 UpbitPage에서 종목코드조회 API를 호출하여 코인목록을 다시 받아옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752130219185&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import './TabSelector.css'
import React from 'react'
import { useUpbitStore , MarketTab } from '@/stores/upbitStore'

const tabs: MarketTab[] = ['KRW', 'BTC', 'USDT']

const TabSelector = () =&amp;gt; {

  const tab = useUpbitStore((state) =&amp;gt; state.tab)
  const setTab = useUpbitStore((state) =&amp;gt; state.setTab)

  return (
    &amp;lt;div className=&quot;tab-container&quot;&amp;gt;
      &amp;lt;ul className=&quot;tab-list&quot;&amp;gt;
        {tabs.map((type) =&amp;gt; (
          &amp;lt;li
            key={type}
            className={tab === type ? 'active' : ''}
            onClick={() =&amp;gt; {  if (tab !== type) setTab(type)}}
          &amp;gt;
            {type}
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default React.memo(TabSelector)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-8050-a3f6-dd1b98c31330&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;SearchBar.tsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-80ac-ad25-faedbd33dcc7&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코인을 검색할 수 있는 컴포넌트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-8042-861d-c6a66b31a565&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자가 검색영역에 검색어를 입력하면 search, visible 상태가 업데이트되고, 업데이트된 상태를 기반으로 코인목록이 필터링되어 화면에 반영됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752130241543&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import './SearchBar.css';
import React from 'react';
import { useUpbitStore } from '@/stores/upbitStore';

const SearchBar = () =&amp;gt; {
 
  const search = useUpbitStore((state) =&amp;gt; state.search);
  const setSearch = useUpbitStore((state) =&amp;gt; state.setSearch);

  return (
    &amp;lt;div className=&quot;search-upbit&quot;&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        placeholder=&quot;검색어 입력&quot;
        value={search}
        onChange={(e) =&amp;gt; setSearch(e.target.value)}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default React.memo(SearchBar);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-8057-a954-f05286236f68&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 주요 기능 구현이 완료되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2176f578-3fe1-80ef-a347-c0f0e6cd3da9&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;구현한 기능을 바탕으로 새로운 기능을 시도해보고, 성능 개선이나 디자인 향상을 통해 프로젝트를 한 층 더 발전시켜보는것도 좋은 학습이 될 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;2166f578-3fe1-809c-b617-f0e08f7ff802&quot; style=&quot;color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image 2.png&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWhSD/btsPbKnI2Mk/kUhNEHk4TBPd2v482eG9D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWhSD/btsPbKnI2Mk/kUhNEHk4TBPd2v482eG9D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWhSD/btsPbKnI2Mk/kUhNEHk4TBPd2v482eG9D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWhSD%2FbtsPbKnI2Mk%2FkUhNEHk4TBPd2v482eG9D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;971&quot; height=&quot;1010&quot; data-filename=&quot;image 2.png&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;1010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React 의 기능들을 더 깊이 이해하고 싶다면 바랍니다 . 제목 없음 &lt;a href=&quot;https://ko.react.dev/&quot;&gt;https://ko.react.dev/&lt;/a&gt; 도 함께 참고해보시기 바랍니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>react</category>
      <category>react실시간코인시세</category>
      <category>upbitapi시세사이트</category>
      <category>사이버이메지네이션</category>
      <category>실시간코인시세사이트</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/318</guid>
      <comments>https://cyberx.tistory.com/318#entry318comment</comments>
      <pubDate>Fri, 11 Jul 2025 14:00:08 +0900</pubDate>
    </item>
    <item>
      <title>[React] React + Upbit API로 실시간 코인 시세 사이트 만들기 (1)</title>
      <link>https://cyberx.tistory.com/317</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 앞선 포스팅에서 소개한 React 와 Upbit API 를 활용하여 간단한 예제를 직접 구현해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 목차 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. 프로젝트 세팅&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. Upbit API 와 WebSocket 연동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 프로젝트 세팅 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;UI 구성에 앞서 React 프로젝트에서는 각 기능별로 폴더를 나눠서 관리하는것이 일반적입니다. 이렇게 구조화하면 유지보수와 확장에 유리하고, 협업시에도 각자의 역할이 명확해집니다. 아래는 대표적인 폴더구조 예시입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; components &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;재사용 가능한 UI 요소들을 보관하는 폴더입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;라우팅에 대응되는 페이지 단위 컴포넌트를 보관합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;URL 경로와 직접 연결되며, 각 화면의 중심역할을 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;hooks &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 전반에서 사용할 수 있는 커스텀 훅들을 정의합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;utils &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; router&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; store &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt; 이처럼 컴포넌트와 기능별로 폴더를 구분하면 구조가 체계적으로 정리되어 각 컴포넌트의 역할이 명확해지고, 재사용 가능한 컴포넌트들을 별도로 관리함으로써, 동일한 기능을 여러 화면에서 사용할 수 있어 개발 효율이 향상되는 장점을 가집니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 각 컴포넌트가 독립적으로 동작하도록 구성되기 때문에 기능을 추가하거나 변경하더라 도여러 부분에 영향을 최소화 할 수 있기 때문에 유지보수가 수월해집니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; App.tsx &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;App 은 프로젝트의 루트 컴포넌트로 전체 애플리케이션의 진입점 역할을 수행합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;router 를 사용하여 페이지 간 전환이 이루어지며 , RouterProvider 태그를 통해 라우팅 기 능을 전역으로 전용합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;화면 전환은 router 설정에 따라 이루어지고, url 경로에 따라 알맞은 페이지 컴포넌트를 렌더링합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751609459153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { RouterProvider } from 'react-router-dom';
import {router} from '@/router';
function App() {
  return (
    &amp;lt;RouterProvider router = {router}/&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Router &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;router 에서는 createBrowserRouter 를 사용하여 라우팅 설정을 객체로 정의하고&amp;nbsp; 이를 외부로 내보냅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;루트 경로로 접속 시 &amp;lt;UpbitPage / &amp;gt; 컴포넌트가 화면에 렌더링됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751609483473&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createBrowserRouter } from &quot;react-router-dom&quot;;
import UpbitPage from &quot;./pages/UpbitPage&quot;;
export const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &amp;lt;UpbitPage /&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; UpbitPage.tsx &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;UpbitPage 는 해당 예제의 메인 화면 컴포넌트입니다. UpbitPage 는 크게 세가지 UI 영역 으로 구성할 예정입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* SearchBar: 코인 검색 입력 필드 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* TabSelector: 코인 시장 구분 탭 (KRW, BTC 등 ) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* ProductTable: 화면에 보여질 코인 목록 테이블 ( 필터링된 코인 리스트 )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; UpbitPage에서는 Upbit 의 API 와 WebSocket 을 통해 코인목록 및 실시간데이터를 가져 오도록 구현합니다. 페이지가 마운트되거나, 탭이 변경될때마다 API 를 호출하여 코인목록을 가져와 현재 선택된 탭에 해당하는 코인만 필터링하여 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751609572136&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect } from 'react';
import { useUpbitStore } from '@/stores/upbitStore';
import { useUpbitSocket } from '@/hooks/useUpbitSocket';

import SearchBar from '@/components/upbit/SearchBar';
import TabSelector from '@/components/upbit/TabSelector';
import ProductTable from '@/components/upbit/ProductTable';

const UpbitPage = () =&amp;gt; {
  const tab = useUpbitStore((state) =&amp;gt; state.tab);
  const setProducts = useUpbitStore((state) =&amp;gt; state.setProducts);

  // 실시간 WebSocket 연결 시작
  useUpbitSocket();

  useEffect(() =&amp;gt; {
  // 코인 리스트 가져오기
    const fetchProducts = async () =&amp;gt; {
      const response = await fetch('https://api.upbit.com/v1/market/all?isDetails=false');
      const data = await response.json();
      const filtered = data.filter((item: any) =&amp;gt; item.market.startsWith(tab)).map((item: any) =&amp;gt; ({code: item.market, korean_name: item.korean_name,}));
    
      setProducts(tab, filtered);
    }

    fetchProducts();
  }, [tab,setProducts])

  return (
    &amp;lt;div className=&quot;upbit&quot;&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;SearchBar /&amp;gt;
        &amp;lt;TabSelector /&amp;gt;
        &amp;lt;ProductTable /&amp;gt;
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default UpbitPage;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 여기까지 메인 컴포넌트와 검색, 탭, 테이블 컴포넌트를 분리하여 구성해보았습니다. 다음단계에서는 업비트 API 를 연동하여 실제 데이터를 받아와 테이블에 정보를 표시하는 기능을 구현해보겠습니다.&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; Upbit API 와 WebSocket 연동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 이번 예제에서는 업비트에서 제공하는 종목코드조회 API 와 현재가를 조회하는 WebSocket을 사용합니다. 하단의 사이트에서 요청과 응답 형식의 예시를 확인할 수 있습니다 . &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 종목코드조회 정보 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://docs.upbit.com/kr/reference/general-info&quot;&gt;https://docs.upbit.com/kr/reference/general-info&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;font-family: 'Nanum Gothic';&quot;&gt;업비트에서 거래 가능한 종목 목록을 조회합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;API URL :&amp;nbsp; &lt;a href=&quot;https://api.upbit.com/v1/market/all&quot;&gt;https://api.upbit.com/v1/market/all&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;font-family: 'Nanum Gothic';&quot;&gt;2. 실시간 시세 WebSocket 정보 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://docs.upbit.com/kr/reference/general-info&quot;&gt;https://docs.upbit.com/kr/reference/general-info&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;font-family: 'Nanum Gothic';&quot;&gt;현재가를 조회합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;WebSocket URL : wss://api.upbit.com/websocket/v1 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;종목코드조회 API 호출 및 상태관리 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;종목코드조회 API 를 호출하기 전에 상태관리에 대해 알아보겠습니다.&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; upbitStore.ts &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 예제에서는 Zustand 라이브러리를 사용하여 가볍고 간단하게 전역 상태를 관리하도록 구성하였습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Product, MarketTab, UpbitState 상태들을 관리하고 setTab, setSearch, setProducts, updateTicker 함수들을 통해 상태를 갱신합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751609617071&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { create } from 'zustand';

export type Product = {
  code: string;
  korean_name: string;
  trade_price?: number;
  change_rate?: number;
  change?: string;
};

export type MarketTab = 'KRW' | 'BTC' | 'USDT';

type UpbitState = {
  tab: MarketTab; // 선택된 탭
  search: string; // 검색어
  products: Record&amp;lt;MarketTab, Product[]&amp;gt;; // 탭별 코인 목록(원본 데이터 저장)
  visible: Product[]; // 화면에 보여줄 코인 목록(검색, 필터링된 목록)
  tickers: Record&amp;lt;string, any&amp;gt;; // 실시간 가격 정보
  prevPrices: Record&amp;lt;string, number&amp;gt;; // 이전 가격 (가격 변화 감지용)

  setTab: (tab: MarketTab) =&amp;gt; void;
  setSearch: (keyword: string) =&amp;gt; void;
  setProducts: (tab: MarketTab, list: Product[]) =&amp;gt; void;
  updateTicker: (data: any) =&amp;gt; void;
};

export const useUpbitStore = create&amp;lt;UpbitState&amp;gt;((set, get) =&amp;gt; ({
  tab: 'KRW',
  search: '',
  products: { KRW: [], BTC: [], USDT: [] },
  visible: [],
  tickers: {},
  prevPrices: {},

  // 탭 변경 함수
  setTab: (tab) =&amp;gt; {
    console.log('현재탭 : ' + tab);
    set({ tab });
  },

  // 검색어 입력 시 호출
  setSearch: (keyword) =&amp;gt; {
    const tab = get().tab;
    const all = get().products[tab];
    const filtered = keyword
      ? all.filter((p) =&amp;gt; p.korean_name.includes(keyword))
      : all;
    set({ search: keyword, visible: filtered });
  },

  // 코인 목록 저장 (API 호출 후)
  setProducts: (tab, list) =&amp;gt; {
    set((state) =&amp;gt; {
      const next = { ...state.products, [tab]: list };
      const visible =
        tab === state.tab
          ? state.search
            ? list.filter((p) =&amp;gt; p.korean_name.includes(state.search))
            : list
          : state.visible;
      return { products: next, visible };
    });
  },

  // 실시간 데이터 업데이트
  updateTicker: (data) =&amp;gt; {
    const { tickers, prevPrices } = get();
    const prev = tickers[data.code]?.trade_price ?? 0;
    const current = data.trade_price ?? 0;

    if (prev === current) return; // 가격변동 없으면 set 안 함

    set({
      tickers: { ...tickers, [data.code]: data },
      prevPrices: { ...prevPrices, [data.code]: prev },
    });
  },
}));
//?? 0 -&amp;gt; 값이 undefined나 null이 나왔을 때 비교오류 방지용 (기본값으로 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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 다음으로 종목코드조회 API 호출단계입니다. 이전에 확인했던 UpbitPage 로직을 다시 살펴 보겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;fetch 함수를 사용하여 Upbit API 를 호출하고, 모든 코인 목록을 받아와 JSON 형식으로 파싱합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파싱한 데이터중 현재 탭에 해당하는 데이터들만 필터링 후, 마켓코드와 코인이름 데이터를 꺼내서 setProducts 함수를 통해 저장합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;종목코드조회 API 호출을 위해 UseEffect 를 사용하여 컴포넌트가 마운트되거나 Tab 이 변경될 때 실행되도록 설정하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751609645864&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    // 코인 리스트 가져오기
    const fetchProducts = async () =&amp;gt; {
      const response = await fetch('https://api.upbit.com/v1/market/all?isDetails=false');
      const data = await response.json();
      const filtered = data.filter((item: any) =&amp;gt; item.market.startsWith(tab)).map((item: any) =&amp;gt; ({code: item.market, korean_name: item.korean_name,}));
    
      setProducts(tab, filtered);
    }

    fetchProducts();
  }, [tab,setProducts])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;LifeCycle &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;useEffect 를 알아보기에 앞서 React의 LifeCycle 에 대해 알아보도록 하겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React의 컴포넌트는 다음 세가지의 생애 주기를 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* Mount = 컴포넌트가 처음 DOM에 나타날 때 ( 화면에 처음 렌더링될 때 ) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* Update = props 또는 state 가 변경되어 컴포넌트가 다시 렌더링될 때 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* Unmount = 컴포넌트가 DOM 에서 제거될 때 ( 화면에서 제거될때 ) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;UseEffect 를 사용하면 LifeCycle의 특정 시점마다 작업을 수행할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; UseEffect 란&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; React 컴포넌트의 생명주기 시점에 특정 작업 ( 사이드 이펙트 )을 수행할 수 있게 해주는 Hook 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751609687556&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  // 실행할 코드 (side effect)
  
  return () =&amp;gt; {
    // (선택) 컴포넌트 언마운트 시 실행될 정리(clean-up) 함수
  };
}, [의존성배열]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; UseEffect 는 다음과 같은 구조이고 , 의존성배열에 따라 실행 시점을 설정할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;의존성배열이 빈경우 ( [] ) 컴포넌트가 처음 마운트될 때 한 번만 실행되고, [A, B]를 작성하 면 A 또는 B 값이 변경될 때 실행됩니다. return 안에 함수는 컴포넌트가 언마운트 될 때 실행 됩니다. 두번째 인수인 의존성배열이 생략되었을 경우에는 매 렌더링될 때마다 실행되므로 비효율적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; WebSocket 연결 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실시간 시세를 받아오기위해 WebSocket 을 연결해보겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 UseEffect 를 사용하여 최초 마운트가 되었을 때 WebSocket 을 연결하도록 로직을 구현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최초 구독은 visible 기준 즉 , 현재 화면에 보여줄 코인목록을 기준으로 전송합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서버에서 오는 실시간 시세정보는 updateTicker 함수를 통해 전역상태에 반영되도록 설정 하였습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;탭또는 검색어입력등의 이유로 화면에 보여줄 코인목록이 변경되었을 경우, 코인목록에 맞 는실시간 시세정보를 받아와야 하기 때문에 다시 구독을 전송하도록 구현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751610020722&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// WebSocket 커스텀훅

import { useEffect, useRef } from 'react';
import { useUpbitStore } from '@/stores/upbitStore';

export const useUpbitSocket = () =&amp;gt; {
  const socketRef = useRef&amp;lt;WebSocket | null&amp;gt;(null);
  const upbitApi = 'wss://api.upbit.com/websocket/v1';

  const visible = useUpbitStore((state) =&amp;gt; state.visible);
  const updateTicker = useUpbitStore((state) =&amp;gt; state.updateTicker);

  // WebSocket 연결은 최초 마운트 시 한 번만 실행
  useEffect(() =&amp;gt; {
    console.log('[WebSocket] 연결 시작');
    socketRef.current = new WebSocket(upbitApi);

    socketRef.current.onopen = () =&amp;gt; {
      console.log('[WebSocket] 연결 완료');

      // 최초 visible 기준으로 구독 전송
      const codes = useUpbitStore.getState().visible.map((coin) =&amp;gt; coin.code); //getState() 로 접근시 이때는 값 보임
      sendSubscribeMsg(codes);
    };

    socketRef.current.onmessage = async (e) =&amp;gt; {
      const data = await new Response(e.data).json();
      updateTicker(data);
    };

    socketRef.current.onclose = () =&amp;gt; {
      console.log('WebSocket 연결 종료');
    };

    socketRef.current.onerror = (e) =&amp;gt; {
      console.error('WebSocket 에러 :', e);
    };

    return () =&amp;gt; {
      socketRef.current?.close();
    };
  }, []);

  // visible이 바뀔 때마다 send()
  useEffect(() =&amp;gt; {
    if (!visible.length) return;
    //console.log(`socketRef.current?.readyState ----&amp;gt; ${socketRef.current?.readyState}` )
    if (socketRef.current?.readyState !== WebSocket.OPEN) return; // WebSocket.OPEN == 1

    const codes = visible.map((coin) =&amp;gt; coin.code);
    sendSubscribeMsg(codes);
  }, [visible]);

  // 구독 전송 함수
  const sendSubscribeMsg = (codes: string[]) =&amp;gt; {
    const msg = JSON.stringify([
      { ticket: 'react-upbit' },
      { type: 'ticker', codes },
      { format: 'DEFAULT' },
    ]);

    try {
      socketRef.current?.send(msg);
    } catch (err) {
      console.error('WebSocket 전송 실패:', 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;font-family: 'Nanum Gothic';&quot;&gt; 결론적으로 현재 탭 ( 마켓 )에 맞는 종목코드조회 API 를 요청 후 , 받아온 데이터를 전역상태에 저장하고, 상태에 따라 해당 코인들의 실시간 가격을 웹소켓으로 구독하고, 받아온 실시간 데이터를 상태에 반영하게 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 종목코드조회 API 와 WebSocket 연동에 대해 알아보았습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음시간에는 받아온 실시간 데이터를 실제 화면에 보여주는 부분을 구현해보겠습니다. &lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>api실시간</category>
      <category>api실시간코인시세</category>
      <category>react</category>
      <category>react upbit</category>
      <category>upbit api websocket</category>
      <category>websocket</category>
      <category>리액트</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/317</guid>
      <comments>https://cyberx.tistory.com/317#entry317comment</comments>
      <pubDate>Fri, 4 Jul 2025 15:17:51 +0900</pubDate>
    </item>
    <item>
      <title>[React] React 소개 및 프로젝트 생성</title>
      <link>https://cyberx.tistory.com/316</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 React가 어떤 라이브러리인지 간단히 살펴보고, Vite와 TypeScript를 활용해 React 개발 환경을 구성하는 방법을 소개해 드리겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;목차&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. React란&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 프로젝트 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. React란 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React 는 사용자 인터페이스 (UI) 를 효율적으로 구축할 수 있도록 도와주는 JavaScript 라 이브러리입니다. Meta(Facebook) 에서 개발되었으며, 현재는 다양한 웹 서비스와 애플리케이션에서 널리 사용되고 있습니다.&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; React의 주요 특징&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;컴포넌트 기반 구조 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React 는 화면을 컴포넌트라는 작은 단위로 나눠서 구성합니다. 예를 들어 웹 페이지가 있다고 하면 헤더는 본문은, 푸터는 이렇게 각각의 부분을 독립적인 파일로 만들고, 조립하듯이 붙여서 하나의 앱을 만듭니다. 이컴포넌트들은 독립적이고 재사용이 가능하며, 하나의 컴포넌트를 수정하면 이 컴포넌트 를사용하는 모든 곳에 자동으로 반영되어 유지보수가 쉽고 유연한 개발이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 선언적 방식과 자동 업데이트 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React 는 선언형 프로그래밍 방식을 따릅니다. 개발자는 UI 의 결과만 선언하면, React 가 상태 (state) 에 따라 어떤 UI 를 보여줘야 하는지 판단하고, DOM 업데이트는 자동으로 처리합니다. 즉 제목 없음, 상태에 따라 어떤 UI 가 나타나야 하는지를 선언하기만 하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; Virtual DOM 을 이용한 고성능 렌더링 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기존의 javascript 개발에서는 DOM 을 직접 조작했기 때문에 성능 저하가 자주 발생했습니다. React 에서는 이를 개선하기 위해 Virtual DOM 을 사용합니다. React 는 브라우저의 실제 DOM 대신 메모리상의 가상 DOM 을 먼저 업데이트한 후, 기존 Virtual DOM과 변경된 Virtual DOM을 비교하여 실제 DOM 에 꼭 필요한 부분만 업데이트 합니다. 이방식은 불필요한 DOM 조작을 줄여주어, 복잡한 UI 에서도 빠르고 부드러운 사용자 경험을 제공할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; JSX 문법 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React 는 HTML 과 유사한 문법인 JSX(JavaScript XML)을 사용합니다. JSX 를 활용하면 자바스크립트 코드 안에서 마치 HTML 처럼 UI 를 구성할 수 있어 가독성이 좋고 작성이 직관적입니다.&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 프로젝트 생성 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지금까지 리액트에 대해 알아보았으니, 이제 프로젝트 개발 환경 세팅을 해보도록 하겠습니다. 이번 프로젝트에서는 Vite 와 TypeScript를 사용하여 빠르고 효율적인 React 개발 환경을 구성할 예정입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Vite &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기존의 CRA(Create React App) 보다 훨씬 빠른 빌드 속도와 개발 서버 반응 속도를 제공합니다. 브라우저가 지원하는 ES Modules 를 활용하여 불필요한 번들링 없이 개발이 가능합니다. 또한 내부적으로 esbuild 를 사용하여 의존성 처리 속도도 매우 빠릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;TypeScript &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;정적 타입을 지원하는 JavaScript 의 상위 언어입니다. javascript 의 유연함은 유지하면서, 컴파일 단계에서 오류를 사전에 탐지할 수 있어 더욱 안정적인 개발이 가능합니다. 또한 코드의 구조와 타입이 명확해지기 때문에 프로젝트 규모가 커질수록 유지보수와 협업에 유리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; Node.js 설치 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 , Node.js 공식 사이트에서 Node.js 를 설치해 주세요. ( 권장 : LTS 버전 )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://nodejs.org/en.&quot;&gt;https://nodejs.org/en.&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;원하는 위치에 react 프로젝트 생성 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;터미널을 열고 아래 명령어를 실행해 프로젝트를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;npm create vite@latest&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t75Yl/btsOUqWMDOx/svvl6cGFl5yUK0ACXnjyak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t75Yl/btsOUqWMDOx/svvl6cGFl5yUK0ACXnjyak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t75Yl/btsOUqWMDOx/svvl6cGFl5yUK0ACXnjyak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft75Yl%2FbtsOUqWMDOx%2Fsvvl6cGFl5yUK0ACXnjyak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;534&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;760&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt; 의존성 설치 및 프로젝트 실행 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;필요한 패키지를 설치하고 개발 서버를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;npm install npm i&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;npm run dev&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image 1.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFi9Wx/btsOTVCWbdL/NZrs5OaRJaJpG552TCUj6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFi9Wx/btsOTVCWbdL/NZrs5OaRJaJpG552TCUj6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFi9Wx/btsOTVCWbdL/NZrs5OaRJaJpG552TCUj6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFi9Wx%2FbtsOTVCWbdL%2FNZrs5OaRJaJpG552TCUj6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;731&quot; height=&quot;541&quot; data-filename=&quot;image 1.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;정상적으로 실행되면 브라우저에서 &lt;b&gt;Vite 기본 페이지&lt;/b&gt;를 확인할 수 있습니다 .&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지금까지 React 를 이용해 프로젝트 생성부터 실행까지 진행해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/&quot;&gt;https://ko.legacy.reactjs.org/&lt;/a&gt; 에서 좀 더 심화된 내용을 제공하고 있으니, React 를 더 사용해보고 싶은 분들은 참고하면 좋을 듯 합니다 .&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>react</category>
      <category>react프로젝트생성</category>
      <category>리액트</category>
      <category>사이버이메지네이션</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/316</guid>
      <comments>https://cyberx.tistory.com/316#entry316comment</comments>
      <pubDate>Fri, 27 Jun 2025 13:58:39 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (2)</title>
      <link>https://cyberx.tistory.com/315</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 지난 포스팅에서 구현한 실시간 코인 시세 사이트에 WebSocket을 활용한 실시간 데이터를 구현하고, 데이터를 가공하고 변화를 감지하여 UI에 반영하는 방법을 함께 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt;&lt;b&gt;을 활용한 실시간 데이터 구현&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;데이터 가공과 UI 수정&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. WebSocket&lt;/b&gt;&lt;b&gt;을 활용한 실시간 데이터 구현&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실습을 진행하기 전, Vue의 Lifecycle Hooks에 대해 알아보도록 하겠습니다.&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: #1a5490;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;|&lt;/span&gt; Lifecycle Hooks&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Vue에서는 컴포넌트가 생성되고 제거되는 일련의 흐름(생명주기)에 따라 특정 시점마다 작업을 수행할 수 있도록 라이프사이클 훅이라는 개념을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Composition API에서는 각 훅을 함수 형태로 가져와서 실행하며, 컴포넌트의 생성, 마운트, 업데이트, 언마운트 등 다양한 시점에 적절한 로직을 넣을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;onMounted : 컴포넌트가 DOM에 마운트된 직후 실행됨&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: left;&quot;&gt;onUnmounted : 컴포넌트가 마운트 해제된 후 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: left;&quot;&gt;onBeforeMount : 컴포넌트가 마운트되기 직전에 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: left;&quot;&gt;onBeforeUnmount : 컴포넌트가 마운트 해제되기 직전에 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: left;&quot;&gt;onUpdated : 컴포넌트의 DOM이 업데이트된 후 실행됨&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이처럼 Vue 3의 라이프사이클 훅은 &lt;b&gt;컴포넌트의 흐름을 따라 코드의 실행 시점을 세밀하게 제어&lt;/b&gt;할 수 있는 중요한 도구입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;WebSocket 연결은 onMounted 훅을 활용해 컴포넌트가 화면에 표시된 직후 실행 되도록 구성합니다. 컴포넌트가 제거된 직후에 실행되는 onUnmounted훅에서는 WebSocket을 종료하여 리소스 낭비를 방지합니다. 수신한 WebSocket 데이터를 저장할 반응형 객체를 스토어에 추가한 뒤, 데이터 수신시 해당 객체에 갱신합니다.&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;store : &lt;/span&gt;stores/upbit.js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745288244421&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const productsTicket = ref({}) // 응답값을 저장할 객체를 추가합니다.

return {
        consts,
        productGroup,
        productsTicket, // 추가한 객체를 return합니다.
        codes,
        getProducts
    }&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;components : &lt;/span&gt;productList.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745288265309&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { productGroup, productsTicket } = storeToRefs(upbitStore) //productTicket을 추가합니다.

let socket = null

onMounted(async () =&amp;gt; {
    await upbitStore.getProducts(); // onMounted 내부로 이동합니다.
    socket = new WebSocket(&quot;wss://api.upbit.com/websocket/v1&quot;);

    socket.onopen = () =&amp;gt; {
    socket.send(
      `${JSON.stringify([
        { ticket: &quot;test example&quot; },
        { type: &quot;ticker&quot;, codes: getCodes(productGroup.value['KRW']) },
        { format: &quot;DEFAULT&quot; },
      ])}`
    );

    socket.onmessage = async (data) =&amp;gt; {
      const ticket = await new Response(data.data).json();
	  // 실시간 가격 데이터를 code별로 저장합니다.
      productsTicket.value[ticket.code] = {
        code: ticket.code,
        ticket: ticket,
      };
    };
  };
});

onUnmounted(() =&amp;gt; {
  if (socket &amp;amp;&amp;amp; socket.readyState === WebSocket.OPEN) {
    socket.close()
  }
})

// 코드 목록을 만들어 반환합니다.
function getCodes(list = []) {
  const codes = []
  for (let code in list) {
    codes.push(code)
  }
  return codes
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자식 컴포넌트에서는 props를 사용하지 않더라도 같은 스토어를 import하여 상태값을 직접 사용할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;WebSocket으로부터 전달된 데이터가 스토어 내부 상태에 반영되면, 이를 참조하는 템플릿 내 {{변수명}} 형태로 바인딩된 값에도 자동으로 반영되어 실시간으로 UI가 업데이트됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스토어 내부 객체에서 특정 종목의 키 값으로 접근하여 현재가를 가져옵니다. 이 때 옵셔널 체이닝(?.)을 활용하면 데이터가 아직 존재하지 않는 경우에도 에러 없이 안전하게 렌더링할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;components : &lt;/span&gt;productItem.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745288322468&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 스토어 내부 상태값 사용을 위해 추가합니다.
const upbitStore = useUpbitStore()
const { productsTicket } = storeToRefs(upbitStore)

// 값을 바인딩합니다.
{{productsTicket[props.product.key]?.ticket?.trade_price }} //현재가
{{productsTicket[props.product.key]?.ticket?.signed_change_rate}} //전일대비&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;WebSocket을 활용한 실시간 데이터 구현이 완료되었습니다. 다음 단계에서는 사용자들에게 더 직관적으로 보여줄 수 있도록 수신한 데이터를 가공하고 UI를 개선해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-1. computed&lt;/b&gt;&lt;b&gt;를 활용한 데이터 포맷팅과 스타일 동적 적용&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실습에 앞서 computed 속성에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #1a5490;&quot;&gt; &lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;|&lt;/span&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; text-align: start;&quot;&gt;computed&lt;/span&gt; &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Vue에서 사용하는 계산된 속성으로 기존의 반응형 데이터를 기반으로 파생 데이터를 선언적으로 정의할 수 있는 기능입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;내부적으로 의존성 추적과 캐싱 기능이 함께 동작하기 때문에 불필요한 연산을 줄이고 효율적인 렌더링을 가능하게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;computed 속성을 사용해 store에 저장된 실시간 데이터를 가공하겠습니다. 가격이 1보다 크면 천 단위 콤마가 포함된 문자열로 변환하고, 1 이하일 경우에는 소수점 둘째 자리까지 고정된 값으로 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745288382156&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const priceValue = computed(() =&amp;gt; {
  const price = productsTicket.value[props.product.key]?.ticket.trade_price

  if (!price) {
    return &quot;0.00&quot;
  }

  if (price &amp;gt; 1) {
    return price.toLocaleString(&quot;en&quot;)
  }

  const fixedValue = 2

  return price.toFixed(fixedValue)
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전일 대비를 %로 변환하여 소수점 두자리까지 표현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745288394352&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const rateValue = computed(() =&amp;gt; {
  const rate = productsTicket.value[props.product.key]?.ticket.signed_change_rate

  if (!rate) {
    return '0.00'
  }

  return (rate.toFixed(8) * 100).toFixed(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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;상승, 하락, 보합에 따라 텍스트의 색상을 변경하여 적용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745288406128&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const textClass = computed(() =&amp;gt; {
  const change = productsTicket.value[props.product.key]?.ticket.change;
  if (!change) {
    return &quot;&quot;;
  }
  if (change === &quot;RISE&quot;) {
    return &quot;text-danger&quot;;
  } else if (change === &quot;FALL&quot;) {
    return &quot;text-primary&quot;;
  } else if (change === &quot;EVEN&quot;) {
    return &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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;computed를 통해 계산된 값을 템플릿 문법({{ }})을 통해 바인딩합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;클래스에 사용할 값은 :class(v-bind:class)를 통해 동적으로 적용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745288431562&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;td class=&quot;alignRight&quot; :class=&quot;textClass&quot;&amp;gt;
    &amp;lt;div&amp;gt;{{ priceValue }}&amp;lt;/div&amp;gt;
    &amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&quot;alignRight&quot; :class=&quot;textClass&quot;&amp;gt;
    {{ rateValue }}
&amp;lt;/td&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-2. watch&lt;/b&gt;&lt;b&gt;를 활용한 UI 효과&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로, 가격이 변동되었을 때 사용자가 인식하기 쉽도록 주변에 박스 형태로 깜빡이는 blink 효과를 추가해 보겠습니다. 가격이 변동되었을 때를 감지하기 위해서 watch를 활용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #1a5490;&quot;&gt; &lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;|&lt;/span&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; text-align: start;&quot;&gt;watch&lt;/span&gt; &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Vue에서 특정 반응형 데이터의 변화를 감지하고 그 값이 변경될 때마다 지정된 로직을 실행할 수 있도록 도와주는 반응형 감시 기능으로 비동기 처리, DOM 조작 등 다양한 후속 동작을 실행할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Compositon API에서는 watch()라는 함수를 통해 감시 로직을 구현합니다. 이번 예제에서는 watch()를 통해 실시간 가격 데이터를 감시하고 이전 가격과 새 가격이 달라질 때만 blink 효과가 동작하도록 구현해보겠습니다. &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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;components : &lt;/span&gt;productItem.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745288478891&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 추가
const blinkRed = ref(false)
const blinkBlue = ref(false)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745288492144&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;watch(() =&amp;gt; productsTicket.value[props.product.key]?.ticket.trade_price, (newPrice, oldPrice) =&amp;gt; {
  if (oldPrice &amp;amp;&amp;amp; oldPrice != newPrice) {
    const rate = productsTicket.value[props.product.key]?.ticket.signed_change_rate
    if (rate &amp;amp;&amp;amp; rate &amp;gt; 0) {
      blinkRedPriceBox()
      return
    }
    blinkBluePriceBox()
  }})

function blinkRedPriceBox() {
  if (!blinkRed.value) {
    blinkBlue.value = false
    blinkRed.value = true
  }

  setTimeout(() =&amp;gt; {
    blinkRed.value = false
  }, 200)
}

function blinkBluePriceBox() {
  if (!blinkBlue.value) {
    blinkRed.value = false
    blinkBlue.value = true
  }

  setTimeout(() =&amp;gt; {
    blinkBlue.value = false
  }, 200)
}&lt;/code&gt;&lt;/pre&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;font-family: 'Nanum Gothic';&quot;&gt;스타일을 적용하기 위해 highlight-red, highlight-blue와 같은 클래스를 미리 정의해두고 :class 디렉티브(v-bind:class)를 통해 값을 적용합니다. blinkRed 또는 blinkBlue의 값이 true일때만 해당 클래스가 동적으로 적용되어 일시적으로 박스 테두리가 깜빡이는 blink 효과를 주게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745288516369&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.highlight-red {
  border: 1px solid red !important;
}

.highlight-blue {
  border: 1px solid blue !important;
}&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;pre id=&quot;code_1745288541330&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div :class=&quot;{ 'highlight-red': blinkRed, 'highlight-blue': blinkBlue }&quot;&amp;gt;
	{{ priceValue }}
&amp;lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 실습은 여기서 마무리하겠습니다. 지금까지 구현한 기능을 바탕으로 기능 추가와 성능 최적화, 에러 처리, 디자인 개선을 통해 프로젝트를 한 단계 더 발전시키는 것은 좋은 연습이 될 것입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqsvy/btsNt5T7AD4/co5pdjjvUoMUROKkculu81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqsvy/btsNt5T7AD4/co5pdjjvUoMUROKkculu81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqsvy/btsNt5T7AD4/co5pdjjvUoMUROKkculu81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frqsvy%2FbtsNt5T7AD4%2Fco5pdjjvUoMUROKkculu81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;710&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Vue의 다양한 기능을 더 깊이 이해하고 싶다면&amp;nbsp;&lt;a style=&quot;font-family: 'Nanum Gothic'; color: #000000; text-align: left;&quot; href=&quot;https://vuejs.org/&quot;&gt;https://vuejs.org/&lt;/a&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전편의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;[Vue.js 3.0] (1) Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1)를 보고 싶으시다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래의 URL을 클릭해주세요~&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745288575173&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1)&quot; data-og-description=&quot;안녕하세요.이번 글에서는 앞선 포스팅에서 소개한 Vue 3와 UPbit API를 활용하여 간단한 예제를 직접 구현해보겠습니다.실습 과정에서 Vue의 컴포넌트 구조와 컴포넌트간의 상호작용 방식에 대해&quot; data-og-host=&quot;cyberx.tistory.com&quot; data-og-source-url=&quot;https://cyberx.tistory.com/314&quot; data-og-url=&quot;https://cyberx.tistory.com/314&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgP5H4/hyYH422KI1/enws9QQvfMUKAuQu3kIYPk/img.jpg?width=557&amp;amp;height=337&amp;amp;face=0_0_557_337,https://scrap.kakaocdn.net/dn/YX0YA/hyYH8j2rgS/ZCSmFMhBZ0a0tnfHU1D5zK/img.jpg?width=557&amp;amp;height=337&amp;amp;face=0_0_557_337,https://scrap.kakaocdn.net/dn/ENZgm/hyYFC7c4Xe/2LLQKy8NKVX4DKtNjdAGH0/img.png?width=1054&amp;amp;height=1054&amp;amp;face=0_0_1054_1054&quot;&gt;&lt;a href=&quot;https://cyberx.tistory.com/314&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cyberx.tistory.com/314&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgP5H4/hyYH422KI1/enws9QQvfMUKAuQu3kIYPk/img.jpg?width=557&amp;amp;height=337&amp;amp;face=0_0_557_337,https://scrap.kakaocdn.net/dn/YX0YA/hyYH8j2rgS/ZCSmFMhBZ0a0tnfHU1D5zK/img.jpg?width=557&amp;amp;height=337&amp;amp;face=0_0_557_337,https://scrap.kakaocdn.net/dn/ENZgm/hyYFC7c4Xe/2LLQKy8NKVX4DKtNjdAGH0/img.png?width=1054&amp;amp;height=1054&amp;amp;face=0_0_1054_1054');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요.이번 글에서는 앞선 포스팅에서 소개한 Vue 3와 UPbit API를 활용하여 간단한 예제를 직접 구현해보겠습니다.실습 과정에서 Vue의 컴포넌트 구조와 컴포넌트간의 상호작용 방식에 대해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cyberx.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>프론트엔드</category>
      <category>computed</category>
      <category>computed를 활용한 데이터 포맷</category>
      <category>websocket 데이터구현</category>
      <category>websoket</category>
      <category>사이버이메지네이션</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/315</guid>
      <comments>https://cyberx.tistory.com/315#entry315comment</comments>
      <pubDate>Thu, 24 Apr 2025 09:00:04 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1)</title>
      <link>https://cyberx.tistory.com/314</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;안녕하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 앞선 포스팅에서 소개한 Vue 3와 UPbit API를 활용하여 &lt;b&gt;간단한 예제를 직접 구현&lt;/b&gt;해보겠습니다.&lt;br /&gt;실습 과정에서 Vue의 컴포넌트 구조와 컴포넌트간의 상호작용 방식에 대해서도 함께 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;프로젝트 세팅과 UI 구성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;UPbit API &lt;/b&gt;&lt;b&gt;연동하여 종목명 표시하기&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1-1.&lt;/b&gt; &lt;b&gt;프로젝트 생성 및 VSCode 확장 프로그램 소개&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이전 포스팅에서 Node.js 설치와 프로젝트 생성 방법을 다뤘습니다.&lt;br /&gt;이번에는 동일한 방식으로 Vue 프로젝트를 만들고, 로컬 환경에서 실행해보겠습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745286902036&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Vue.js 3.0] (1) Vue 소개 및 프로젝트 생성&quot; data-og-description=&quot;안녕하세요. 이번 글에서는 웹 개발에서 중요한 역할을 하는 프론트엔드 프레임워크 Vue.js를 설명드리겠습니다. 우선 Vue.js에 대해 알아보고 프로젝트를 생성하며 간단한 예제를 만들어보고, 다&quot; data-og-host=&quot;cyberx.tistory.com&quot; data-og-source-url=&quot;https://cyberx.tistory.com/310&quot; data-og-url=&quot;https://cyberx.tistory.com/310&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d8VFq1/hyYJuAGuAN/4vWOzRDYt7xG15SxVaqvg1/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/WOYfH/hyYJoHgMl3/ukb7VrxOtyBzAq1U732XQK/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/cY11Hn/hyYFDkJFd7/nOxglDGRpdEa2SddqxgFfK/img.png?width=727&amp;amp;height=344&amp;amp;face=0_0_727_344&quot;&gt;&lt;a href=&quot;https://cyberx.tistory.com/310&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cyberx.tistory.com/310&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d8VFq1/hyYJuAGuAN/4vWOzRDYt7xG15SxVaqvg1/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/WOYfH/hyYJoHgMl3/ukb7VrxOtyBzAq1U732XQK/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/cY11Hn/hyYFDkJFd7/nOxglDGRpdEa2SddqxgFfK/img.png?width=727&amp;amp;height=344&amp;amp;face=0_0_727_344');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Vue.js 3.0] (1) Vue 소개 및 프로젝트 생성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 이번 글에서는 웹 개발에서 중요한 역할을 하는 프론트엔드 프레임워크 Vue.js를 설명드리겠습니다. 우선 Vue.js에 대해 알아보고 프로젝트를 생성하며 간단한 예제를 만들어보고, 다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cyberx.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트를 시작하기 전에, 개발 환경을 효율적으로 구성하는데 도움을 줄 수 있는 VSCode 확장 프로그램을 소개합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #1a5490;&quot;&gt; &lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;| &lt;/span&gt;&lt;/b&gt;&lt;b&gt;VSCode &lt;/b&gt;&lt;b&gt;확장프로그램 소개&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1)&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/b&gt;&lt;b&gt;Vue &amp;ndash; Official(Volar)&lt;/b&gt; : Vue 팀에서 공식적으로 제공하는 Vue 3 개발용 확장 (TypeScript 지원 포함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2)&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/b&gt;&lt;b&gt;ESLint&lt;/b&gt; : 코드 문법 및 스타일 검사&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3)&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/b&gt;&lt;b&gt;Prettier&lt;/b&gt; : 코드 포맷 정리 자동화&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4)&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/b&gt;&lt;b&gt;Vue 3 Snippets&lt;/b&gt; : Vue3에 특화된 자동완성 제공&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;위와 같은 확장프로그램을 설치하면 코드 자동완성, 문법 오류 표시, 자동 포맷팅 등 다양한 기능을 통해 개발 생산성을 향상시킬 수 있습니다. 특히 Vue3를 지원하는 확장 프로그램들을 통해 최신 문법을 지원받고 일관된 코드 스타일을 유지할 수 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1-2. UI &lt;/b&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;font-family: 'Nanum Gothic';&quot;&gt;UI 구성에 앞서, Vue 프로젝트에서는 컴포넌트를 역할에 따라 views/와 components/ 로 나누어 관리하는 것이 일반적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #1a5490;&quot;&gt;&lt;b&gt; &lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;|&lt;/span&gt;&lt;/b&gt;&amp;nbsp;views/&lt;/b&gt;&lt;b&gt;와 components/의 차이&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;views/ : &lt;/b&gt;페이지 단위 컴포넌트로 라우터에 직접 연결되는 대상. 화면 전체를 구성하며 여러 개의 하위 컴포넌트를 조합함&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;components/ : &lt;/b&gt;재사용 가능한 UI로 라우터에 직접 연결되지 않으며, 다양한 화면에서 재사용이 가능함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이처럼 컴포넌트를 목적에 따라 나누는 방식은 프로젝트 구조가 체계적으로 정리되어 각 컴포넌트의 역할이 명확해지고 재사용 가능한 UI요소를 따로 분리함으로써 동일한 기능을 여러 화면에서 활용할 수 있어 개발 효율이 향상되는 장점을 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;이번 예제에서도 views/ 디렉토리에 메인 페이지를, components/ 디렉토리에 테이블 영역 컴포넌트를 분리하여 생성하겠습니다.&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메인 페이지 컴포넌트 생성 : &lt;/span&gt;views/upbit/upbitList.vue&lt;/li&gt;
&lt;li style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;테이블 컴포넌트 생성 : &lt;/span&gt;components/products/productList.vue&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;upbitList.vue 파일의 예시코드입니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;테이블 영역은 &amp;lt;ProductList /&amp;gt; 컴포넌트를 사용해 화면에 출력합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745287153805&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;section id=&quot;upbit&quot;&amp;gt;
          &amp;lt;ProductList /&amp;gt;
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/template&amp;gt;
  
  &amp;lt;script setup&amp;gt;
  import ProductList from &quot;@/components/products/productList.vue&quot;;
  &amp;lt;/script&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;font-family: 'Nanum Gothic';&quot;&gt;upbitList.vue 파일은 메인 페이지 역할을 하므로 Vue Router를 통해 해당 컴포넌트를 URL 경로에 연결합니다. 이를 위해 src/router/index.js 파일에 라우팅 설정을 추가합니다. 아래 예시는 초기 진입시 /main으로 이동한 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745287179298&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRouter, createWebHistory } from 'vue-router'
import upbitList from '@/views/upbit/upbitList.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      redirect: { name: 'main' }
    },
    {
        path: `/main`,
        name: 'main',
        component: upbitList,
        props: true
    },
  ],
})

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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;App.vue 파일은 프로젝트의 루트 컴포넌트로 라우터를 통해 연결된 페이지를 출력하는 역할을 합니다.&amp;nbsp; &amp;lt;router-view /&amp;gt; 태그를 통해 현재 경로에 해당하는 컴포넌트를 동적으로 렌더링합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;productList.vue 파일에는 테이블 마크업을 추가하고 스타일을 자유롭게 적용하여 UI구성을 완료합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745287204546&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;contentsArea&quot;&amp;gt;
      &amp;lt;table class=&quot;checkTable&quot;&amp;gt;
        &amp;lt;colgroup&amp;gt;
          &amp;lt;col width=&quot;140px&quot; /&amp;gt;
          &amp;lt;col width=&quot;110px&quot; /&amp;gt;
          &amp;lt;col width=&quot;90px&quot; /&amp;gt;
        &amp;lt;/colgroup&amp;gt;
        &amp;lt;thead&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;th class=&quot;alignCenter&quot; scope=&quot;col&quot;&amp;gt;한글명&amp;lt;/th&amp;gt;
            &amp;lt;th class=&quot;alignCenter&quot; scope=&quot;col&quot;&amp;gt;현재가&amp;lt;/th&amp;gt;
            &amp;lt;th class=&quot;alignCenter&quot; scope=&quot;col&quot;&amp;gt;전일대비&amp;lt;/th&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt; 
              &amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;
            &amp;lt;/td&amp;gt;
            &amp;lt;td class=&quot;alignRight&quot;&amp;gt;
            &amp;lt;/td&amp;gt;
            &amp;lt;td class=&quot;alignRight&quot;&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 메인 페이지와 테이블 컴포넌트를 분리하여 구성해보았습니다. 다음 단계에서는 업비트 API를 연동하여 실제 데이터를 불러오고, 테이블에 정보를 표시하는 기능을 구현해보겠습니다.&lt;/span&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-1. UPbit API &lt;/b&gt;&lt;b&gt;와 WebSocket 소개&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 예제에서는 업비트에서 제공하는 종목 코드 조회 API와 현재가를 조회하는 WebSocket을 사용합니다. 연결된 사이트에서 요청과 응답 형식의 예시를 확인할 수 있습니다.&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 종목 코드 조회&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745287217479&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;업비트 개발자 센터&quot; data-og-description=&quot; &quot; data-og-host=&quot;docs.upbit.com&quot; data-og-source-url=&quot;https://docs.upbit.com/reference/%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C&quot; data-og-url=&quot;https://docs.upbit.com/kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vyxlt/hyYIifT2iN/yFHk4pCJlSCdLj5Tz7ncmK/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80,https://scrap.kakaocdn.net/dn/bdRNeo/hyYFB8ibjx/Pr0AlDTyBj0X2s9zVJzv20/img.png?width=2400&amp;amp;height=1254&amp;amp;face=0_0_2400_1254,https://scrap.kakaocdn.net/dn/GHUVE/hyYFzWV9Bf/LHzjyD2TIl1b2nIKmCjeb1/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80&quot;&gt;&lt;a href=&quot;https://docs.upbit.com/reference/%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.upbit.com/reference/%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vyxlt/hyYIifT2iN/yFHk4pCJlSCdLj5Tz7ncmK/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80,https://scrap.kakaocdn.net/dn/bdRNeo/hyYFB8ibjx/Pr0AlDTyBj0X2s9zVJzv20/img.png?width=2400&amp;amp;height=1254&amp;amp;face=0_0_2400_1254,https://scrap.kakaocdn.net/dn/GHUVE/hyYFzWV9Bf/LHzjyD2TIl1b2nIKmCjeb1/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;업비트 개발자 센터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.upbit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;업비트에서 거래 가능한 종목 목록을 조회합니다&lt;/li&gt;
&lt;li&gt;API URL : &lt;a style=&quot;font-family: 'Nanum Gothic'; letter-spacing: 0px;&quot; href=&quot;https://api.upbit.com/v1/market/all&quot;&gt;https://api.upbit.com/v1/market/all&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #384248;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) 실시간 시세 WebSocket&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745287236565&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;업비트 개발자 센터&quot; data-og-description=&quot; &quot; data-og-host=&quot;docs.upbit.com&quot; data-og-source-url=&quot;https://docs.upbit.com/reference/general-info&quot; data-og-url=&quot;https://docs.upbit.com/kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cK8cs9/hyYIeEz2BH/hxcMlPhWdR5eU8oGUTDsjk/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80,https://scrap.kakaocdn.net/dn/bZ9sAS/hyYH6s0fCw/bokq72mSyZovmPEhGbSSHK/img.png?width=2400&amp;amp;height=1254&amp;amp;face=0_0_2400_1254,https://scrap.kakaocdn.net/dn/bVih8Q/hyYFy4M7QW/2w8KwSeBHiBBO9S6DXXMgK/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80&quot;&gt;&lt;a href=&quot;https://docs.upbit.com/reference/general-info&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.upbit.com/reference/general-info&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cK8cs9/hyYIeEz2BH/hxcMlPhWdR5eU8oGUTDsjk/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80,https://scrap.kakaocdn.net/dn/bZ9sAS/hyYH6s0fCw/bokq72mSyZovmPEhGbSSHK/img.png?width=2400&amp;amp;height=1254&amp;amp;face=0_0_2400_1254,https://scrap.kakaocdn.net/dn/bVih8Q/hyYFy4M7QW/2w8KwSeBHiBBO9S6DXXMgK/img.png?width=354&amp;amp;height=80&amp;amp;face=0_0_354_80');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;업비트 개발자 센터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.upbit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;현재가를 조회합니다.&lt;/li&gt;
&lt;li&gt;WebSocket URL : wss://api.upbit.com/websocket/v1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt;2-2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;종목 코드 조회 API 호출 및 상태 관리 (axios + Pinia)&lt;/b&gt; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;font-family: 'Nanum Gothic';&quot;&gt;앞선 포스팅에서 소개한 Pinia와 axios 통신을 활용하여 API를 호출하고 해당 값을 전역에서 사용해볼 차례입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;axios는 별도의 파일을 만들고 API를 요청하는 로직은 api/ 디렉토리 하위에 별도 파일로 분리해 구성했습니다.&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;axios : &lt;/span&gt;&amp;nbsp;utils/axios.js 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745287336914&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from &quot;axios&quot;;
export const awaitApi = async (method, url, _headers, data) =&amp;gt; {
  const axios = createAxios(_headers);

  if (method == &quot;GET&quot;) {
    try {
      const response = await axios.get(url);
      const responseData = response.data;

      return returnAwaitApi(responseData);
    } catch (e) {
      return returnAwaitApi(e);
    }
  } else if (method == &quot;POST&quot;) {
    try {
      const response = await axios.post(url, data);
      const responseData = response.data;

      return returnAwaitApi(responseData);
    } catch (e) {
      return returnAwaitApi(e);
    }
  }
};

const returnAwaitApi = (data) =&amp;gt; {
  if (data instanceof Error) {
    return {
      success: false,
      result: null,
      error: data,
    };
  }

  return {
    success: true,
    result: data,
    error: false,
  };
};

const createAxios = (_headers) =&amp;gt; {
  return axios.create({
    headers: _headers,
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;API : &lt;/span&gt;api/upbitApi.js 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UPbit API종목 정보를 가져오기 위해 getProducts() 함수를 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745287431772&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { awaitApi } from &quot;@/utils/axios&quot;;
export default {
  async getProducts() {
    return await awaitApi(&quot;GET&quot;, &quot;https://api.upbit.com/v1/market/all&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스토어를 생성하고 반응형 상태값과, api를 호출하여 값을 가공하는 함수를 생성합니다. 실습에서는 우선 KRW마켓의 종목만을 사용하겠습니다. KRW 마켓 종목은 객체 형태로 저장하여 종목 코드(key)로 빠르게 접근할 수 있도록 구성합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스토어 생성 :&lt;/span&gt;&amp;nbsp;stores/upbit.js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745287460888&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ref } from &quot;vue&quot;;
import { defineStore } from &quot;pinia&quot;;
import upbitApi from &quot;@/api/upbitApi.js&quot;;

export const useUpbitStore = defineStore(&quot;upbitStore&quot;, () =&amp;gt; {
    const consts = {
        markets: {
            KRW: 'KRW'
        }
    }

    const allProducts = ref([])
    const krwProducts = ref({})
 
    const productGroup = ref({
        ALL: allProducts,
        KRW: krwProducts
    })

    async function getProducts() {
        const { result } = await upbitApi.getProducts();

        allProducts.value = result.map((item) =&amp;gt; {
            return {
                key: item.market,
                item: item,
            };
        });

        allProducts.value.forEach(item =&amp;gt; {
            const marketName = item.key.split('-')[0]

            if (marketName === consts.markets.KRW) {
                krwProducts.value[item.key] = item
            }
        })
    }

    return {
        consts,
        productGroup,
        getProducts
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-3. &lt;/b&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;font-family: 'Nanum Gothic';&quot;&gt;productList.vue &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파일에서는 getProducts() 함수를 호출하여 종목 데이터를 불러옵니다. 이는 upbitStore에서 API를 통해 데이터를 가져오는 비동기 함수로 다음과 같이 사용됩니&lt;/span&gt;다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745287479856&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useUpbitStore } from &quot;@/stores/upbit.js&quot;;
const upbitStore = useUpbitStore()
await upbitStore.getProducts();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로 각 종목의 데이터를 표시할 테이블의 행 영역을 별도의 컴포넌트로 분리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 행 영역 컴포넌트 생성 : components/products/productItem.vue&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;font-family: 'Nanum Gothic';&quot;&gt;분리된 컴포넌트에 데이터를 전달하기 위해 props를 사용합니다. 이는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #1a5490;&quot;&gt; &lt;b&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;|&lt;/span&gt;&lt;/b&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; text-align: start;&quot;&gt;props란&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;단방향 데이터 흐름 (부모 -&amp;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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;데이터를 컴포넌트 간에 직접 전달하는 방식에는 props와 emit이 있습니다. 두 방식의 차이를 간단히 비교해보고 앞서 사용한 전역 상태 관리 방식인 store와의 차이도 함께 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #1a5490;&quot;&gt; &lt;b&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;|&lt;/span&gt;&lt;/b&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; text-align: start;&quot;&gt;props vs emit vs store&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;props : 부모-&amp;gt;자식으로 데이터를 전달하기 위함 :value=&amp;rdquo;data&amp;rdquo;형태로 명시하는 것이 필요&lt;/li&gt;
&lt;li&gt;emit : 자식-&amp;gt;부모로 이벤트를 전달하기 위함 $emit(&amp;lsquo;event&amp;rsquo;) 형식으로 사용&lt;/li&gt;
&lt;li&gt;store : 전역 상태로 접근이 가능하며 여러 컴포넌트에서 상태 공유를 목적으로 사용함 어디서든 직접 접근이 가능함&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;font-family: 'Nanum Gothic';&quot;&gt;부모 컴포넌트인 productList.vue에서 자식 컴포넌트인productItem.vue로 :product=&amp;rdquo;product&amp;rdquo; 형식을 사용하여 개별종목 데이터를 props로 전달합니다. 자식 컴포넌트에서는 defineProps로 해당값을 전달받아 사용합니다.&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;productList.vue&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745287619971&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;tr v-for=&quot;(product, index) in productGroup['KRW']&quot; :key=&quot;index&quot;&amp;gt;
	&amp;lt;ProductItem :product=&quot;product&quot; /&amp;gt;
&amp;lt;/tr&amp;gt;
// 스토어 값을 가져오기 위해 추가합니다.
import { useUpbitStore } from '@/stores/upbit.js'
import {storeToRefs} from &quot;pinia&quot;;
const upbitStore = useUpbitStore()
const { productGroup } = storeToRefs(upbitStore)&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 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;productItem.vue&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745287638100&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;td&amp;gt;
    &amp;lt;strong&amp;gt;{{ product.item.korean_name }}&amp;lt;/strong&amp;gt;
  &amp;lt;/td&amp;gt;
  &amp;lt;td class=&quot;alignRight&quot;&amp;gt;
  &amp;lt;/td&amp;gt;
  &amp;lt;td class=&quot;alignRight&quot;&amp;gt;
  &amp;lt;/td&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script setup&amp;gt;
import { ref, computed, watch } from &quot;vue&quot;;
import { useUpbitStore } from &quot;@/stores/upbit.js&quot;;
import {storeToRefs} from &quot;pinia&quot;;
const props = defineProps({
  product: {
    type: Object,
  }
});
&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;font-family: 'Nanum Gothic';&quot;&gt;이로써 전체 종목 중 KRW로 거래 가능한 종목을 필터링하여 종목명을 테이블에 표시하는 작업까지 완료하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음 시간에는 &lt;b&gt;WebSocket을 통해 실시간 시세의 변동을 수신하고 테이블에 반영해 실시간 UI 업데이트를 구현&lt;/b&gt;해보겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>vue.js</category>
      <category>vue.js3.0</category>
      <category>사이버이메지네이션</category>
      <category>실시간시세</category>
      <category>실시간코인</category>
      <category>실시간코인시세</category>
      <category>코인시세</category>
      <category>코인시세사이트</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/314</guid>
      <comments>https://cyberx.tistory.com/314#entry314comment</comments>
      <pubDate>Tue, 22 Apr 2025 09:32:12 +0900</pubDate>
    </item>
    <item>
      <title>AWS EC2 활용한 CI/CD 구축 (2)</title>
      <link>https://cyberx.tistory.com/313</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;summary3.png&quot; data-origin-width=&quot;2107&quot; data-origin-height=&quot;1320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DLRMt/btsDY7MprzE/EC9pukuECBo5CxBBcOfg51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DLRMt/btsDY7MprzE/EC9pukuECBo5CxBBcOfg51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DLRMt/btsDY7MprzE/EC9pukuECBo5CxBBcOfg51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDLRMt%2FbtsDY7MprzE%2FEC9pukuECBo5CxBBcOfg51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;345&quot; data-filename=&quot;summary3.png&quot; data-origin-width=&quot;2107&quot; data-origin-height=&quot;1320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;안녕하세요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2편에서는 1편에서 설명드렸던 CI/CD를 실제로 구축해보도록 하겠습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1편을 확인하고 싶으신 분들은 아래 링크를 눌러주세요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://cyberx.tistory.com/312&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS EC2 활용한 CI/CD 구축 (1)&lt;/a&gt;&lt;/span&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;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 아키텍처&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;940&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm8I2r/btsDSrTGGeu/7qGdfpE0YkW0q8v9BEx4P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm8I2r/btsDSrTGGeu/7qGdfpE0YkW0q8v9BEx4P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm8I2r/btsDSrTGGeu/7qGdfpE0YkW0q8v9BEx4P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm8I2r%2FbtsDSrTGGeu%2F7qGdfpE0YkW0q8v9BEx4P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;140&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;개발자 작업사항 커밋 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt; jenkins 빌드 실행 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt; GitHub 프로젝트 파일 빌드 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt; jenkins 빌드한 파일을 배포서버 ec2 인스턴스에 배포 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr; &lt;/span&gt;배포 서버 ec2 톰캣 실행 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2. 구축순서&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;구축은 아래 순서대로 진행하도록 하겠습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 개발 프로젝트 github 연동&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2. AWS 계정 생성&amp;nbsp; &lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;rarr; 2번 단계까지는 별도 설명없습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3. IAM 사용자 추가 (그룹 생성, 권한 추가)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;4. EC2 서비스 인스턴스 생성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;5. EC2 인스턴스 SSH 접속 방법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;6. Git 설치, 자바 설치, 젠킨스 설치 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;7. 젠킨스 파이브라인 스크립트 작성 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;8. 빌드 및 배포 &lt;/span&gt;&lt;/b&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;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3. 구축&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이제 CI/CD를 구축해보도록 하겠습니다.&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;우선 IAM 사용자를 추가해줍니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;접속 경로 : IAM &amp;rarr; 사용자 &amp;rarr; 사용자 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 사용자 이름 입력 &amp;rarr; 2. 사용자 엑세스 권한 제공 체크 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt; 3. &amp;ldquo;IAM 사용자를 생성하고 싶은&amp;rdquo; 체크 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt; 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;940&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eah0AO/btsDXGar5Gs/2V7eR7kPCIkJPOYMHZb3H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eah0AO/btsDXGar5Gs/2V7eR7kPCIkJPOYMHZb3H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eah0AO/btsDXGar5Gs/2V7eR7kPCIkJPOYMHZb3H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feah0AO%2FbtsDXGar5Gs%2F2V7eR7kPCIkJPOYMHZb3H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;386&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 그룹에 사용자 추가 &amp;rarr; 2. 그룹생성 체크 &amp;rarr; 3. ec2FullAccess 권한 추가 &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;940&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oLxJr/btsDQ9Tf6LR/LOjY5grKF7b3LdinZk8970/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oLxJr/btsDQ9Tf6LR/LOjY5grKF7b3LdinZk8970/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oLxJr/btsDQ9Tf6LR/LOjY5grKF7b3LdinZk8970/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoLxJr%2FbtsDQ9Tf6LR%2FLOjY5grKF7b3LdinZk8970%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;263&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;추가한 사용자 디바이스 인증을 추가합니다. (선택 사항 AWS 로그인 시 디바이스 인증 절차가 추가됨)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;접속 경로 : IAM &amp;rarr; 사용자 &amp;rarr; 보안자격 증명 접속 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;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;940&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpJrgY/btsDXaW9TrA/iFDQrjlopFmFlSIjkIr0h1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpJrgY/btsDXaW9TrA/iFDQrjlopFmFlSIjkIr0h1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpJrgY/btsDXaW9TrA/iFDQrjlopFmFlSIjkIr0h1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpJrgY%2FbtsDXaW9TrA%2FiFDQrjlopFmFlSIjkIr0h1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;248&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;디바이스 인증을 하기 위해선 Authenticator 앱 인증이 필요합니다. (앱 스토어를 통해 Authenticator App 다운로드 필요)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2. Authenticator 앱을 접속해 큐알 인증 &amp;rarr; 3. MFA 코드 작성&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;813&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m64dN/btsDUfd4Obm/xkvvv3iKk8ByGg2UozEK2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m64dN/btsDUfd4Obm/xkvvv3iKk8ByGg2UozEK2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m64dN/btsDUfd4Obm/xkvvv3iKk8ByGg2UozEK2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm64dN%2FbtsDUfd4Obm%2Fxkvvv3iKk8ByGg2UozEK2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;522&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;이제 EC2 서비스 인스턴스를 생성해보도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;EC2 서비스는 AWS에서 제공하는 가상 서버 클라우드 컴퓨팅 서비스입니다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;젠킨스 서버와 배포서버 인스턴스 생성합니다&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;접속 경로 : EC2 &amp;rarr; 인스턴스 &amp;rarr; &amp;nbsp;인스턴스 시작&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 인스턴스 이름 입력 &amp;rarr; 2,3. 사용하고자 하는 운영체제 선택 &amp;rarr; 4. 인스턴스 유형 선택 &amp;rarr; 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;940&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XO0E8/btsDUU8BAmO/xedi1aLAozyTk1f6qIwyg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XO0E8/btsDUU8BAmO/xedi1aLAozyTk1f6qIwyg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XO0E8/btsDUU8BAmO/xedi1aLAozyTk1f6qIwyg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXO0E8%2FbtsDUU8BAmO%2Fxedi1aLAozyTk1f6qIwyg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;365&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/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;938&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceZx7r/btsDXFCAz51/ZAj8CAkyZB47bslLbmp241/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceZx7r/btsDXFCAz51/ZAj8CAkyZB47bslLbmp241/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceZx7r/btsDXFCAz51/ZAj8CAkyZB47bslLbmp241/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceZx7r%2FbtsDXFCAz51%2FZAj8CAkyZB47bslLbmp241%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;374&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;생성한 ec2 인스턴스에 접속하기 위해 키패어를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 키 페어 입력 &amp;rarr; 2. 키 페어 생성 클릭&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;940&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwexlz/btsDWp8lHJL/jJl2AzZb9WvpsWHdkMdO41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwexlz/btsDWp8lHJL/jJl2AzZb9WvpsWHdkMdO41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwexlz/btsDWp8lHJL/jJl2AzZb9WvpsWHdkMdO41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwexlz%2FbtsDWp8lHJL%2FjJl2AzZb9WvpsWHdkMdO41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;406&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3. 요약 사항을 확인후 &amp;ldquo;인스턴스 시작&amp;rdquo; 클릭&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;732&quot; data-origin-height=&quot;637&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLmfws/btsDT268Yft/lixeND7kwc9ixtMVIkkD20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLmfws/btsDT268Yft/lixeND7kwc9ixtMVIkkD20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLmfws/btsDT268Yft/lixeND7kwc9ixtMVIkkD20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLmfws%2FbtsDT268Yft%2FlixeND7kwc9ixtMVIkkD20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;566&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;637&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;다음으로 EC2 &lt;/b&gt;&lt;b&gt;인스턴스 ssh 접속 방법을 설정해보도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;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;940&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD3dB6/btsDWrykqJQ/INegCo90cam4HxSARKexY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD3dB6/btsDWrykqJQ/INegCo90cam4HxSARKexY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD3dB6/btsDWrykqJQ/INegCo90cam4HxSARKexY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD3dB6%2FbtsDWrykqJQ%2FINegCo90cam4HxSARKexY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;412&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;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;940&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/riC1N/btsDSqtDuW9/Y2Xb5CuVDhZYE0rhzjtFi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/riC1N/btsDSqtDuW9/Y2Xb5CuVDhZYE0rhzjtFi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/riC1N/btsDSqtDuW9/Y2Xb5CuVDhZYE0rhzjtFi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FriC1N%2FbtsDSqtDuW9%2FY2Xb5CuVDhZYE0rhzjtFi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;434&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;SSH 접속을 위한 MobaXterm 다운로드가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;MobaXterm은 Windows 운영 체제에서 사용할 수 있는 강력한 터미널 및 ssh 클라이언트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1.다운로드한 풀더에서 MobaXterm.exe 실행 &amp;rarr; 2. Session 클릭&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;940&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcHdKq/btsDXaCOSvf/4NQ94jtS6zwNok40rhc8IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcHdKq/btsDXaCOSvf/4NQ94jtS6zwNok40rhc8IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcHdKq/btsDXaCOSvf/4NQ94jtS6zwNok40rhc8IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcHdKq%2FbtsDXaCOSvf%2F4NQ94jtS6zwNok40rhc8IK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;379&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;940&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIPsGK/btsDTuWTAgQ/wPcz00jH1eVZ12JhLVOK21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIPsGK/btsDTuWTAgQ/wPcz00jH1eVZ12JhLVOK21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIPsGK/btsDTuWTAgQ/wPcz00jH1eVZ12JhLVOK21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIPsGK%2FbtsDTuWTAgQ%2FwPcz00jH1eVZ12JhLVOK21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;398&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;이제 Git와 자바를 설치해보도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706143676486&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SSH 접속 후 작업 입니다.
# Git을 설치합니다
sudo yum install git -y
git &amp;ndash;version
# /sw/jvm풀더 생성 
# sw -&amp;gt; jvm 풀더 접속  
# wget 명령어 통해 jdk 다운로드 
wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz
# /sw/jvm 풀더 하위에 파일 풀기
tar -zxvf jdk-17_linux-x64_bin.tar.gz
# 환경변수 설정
vi ~/.bashrc 
# 추가 
export JAVA_HOME=/sw/jvm/jdk-17.0.9
export PATH=$JAVA_HOME/bin:$PATH
source ~/.bashrc&lt;/code&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;jenkins를 설치해보도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706143716522&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SSH 접속 후 작업 입니다. 
# /sw/jenkins풀더 생성 
# sw -&amp;gt; jenkins풀더 접속
wget https://get.jenkins.io/war-stable/2.426.2/jenkins.war
sudo vi /etc/systemd/system/jenkins.service //루트 계정으로
# jenkins.service 안에 작성 
[Unit]
Description=Jenkins Service
After=network.target
[Service]
User=dev_user # 해당 접속 사용자로 변경 필요
ExecStart=/sw/jvm/jdk-17.0.9/bin/java -Djava.awt.headless=true -jar /sw/jenkins/jenkins.war --httpPort=9090
WorkingDirectory=/sw/jenkins
[Install]
WantedBy=default.target  
# 실행
sudo systemctl daemon-reload
sudo systemctl enable jenkins
sudo systemctl start jenkins
# 중지
sudo systemctl stop jenkins 
# 확인
sudo systemctl status jenkins.service
sudo yum install freetype // 자바버전에 따라 라이브러리 추가설치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;설치 완료 후 9090포트를 활성화한 보안그룹(보안그룹 만들기 설명 PASS)을 만들고 추가해줍니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 보안그룹 변경 &amp;rarr; 2.보안그룹 선택 &amp;rarr; 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;668&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8wztN/btsDRHCcT6d/bvQp6PN304qV1P4dGBqmNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8wztN/btsDRHCcT6d/bvQp6PN304qV1P4dGBqmNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8wztN/btsDRHCcT6d/bvQp6PN304qV1P4dGBqmNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8wztN%2FbtsDRHCcT6d%2FbvQp6PN304qV1P4dGBqmNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;551&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/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;668&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJu4E/btsDWKxDEAJ/emY8XntCFRvKs5pqQYdTZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJu4E/btsDWKxDEAJ/emY8XntCFRvKs5pqQYdTZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJu4E/btsDWKxDEAJ/emY8XntCFRvKs5pqQYdTZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJu4E%2FbtsDWKxDEAJ%2FemY8XntCFRvKs5pqQYdTZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;538&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;보안 그룹 추가 후 활성화된 포트로 jenkins 접속합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;720&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nRbO8/btsDW8LOIUG/mSr8CIv3Q2XuAKRrGjs7A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nRbO8/btsDW8LOIUG/mSr8CIv3Q2XuAKRrGjs7A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nRbO8/btsDW8LOIUG/mSr8CIv3Q2XuAKRrGjs7A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnRbO8%2FbtsDW8LOIUG%2FmSr8CIv3Q2XuAKRrGjs7A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;551&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;712&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zl2nG/btsDUJzePd7/xlfG7xfVKUAELrUhAlTzp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zl2nG/btsDUJzePd7/xlfG7xfVKUAELrUhAlTzp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zl2nG/btsDUJzePd7/xlfG7xfVKUAELrUhAlTzp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZl2nG%2FbtsDUJzePd7%2FxlfG7xfVKUAELrUhAlTzp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;552&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Jenkins 계정을 생성합니다.&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;716&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnmrJQ/btsDWSCqh25/5aouGt6XORNK5MQLukG1CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnmrJQ/btsDWSCqh25/5aouGt6XORNK5MQLukG1CK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnmrJQ/btsDWSCqh25/5aouGt6XORNK5MQLukG1CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnmrJQ%2FbtsDWSCqh25%2F5aouGt6XORNK5MQLukG1CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;500&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;이제 jenkins 파이프라인 스크립트를 작성해보도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Item을 생성합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 파이프라인 type 선택 &amp;rarr; 2. 저장&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;940&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CNit2/btsDXzoRDVi/wf0cRYVLgkSPKgo4vsl7o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CNit2/btsDXzoRDVi/wf0cRYVLgkSPKgo4vsl7o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CNit2/btsDXzoRDVi/wf0cRYVLgkSPKgo4vsl7o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCNit2%2FbtsDXzoRDVi%2Fwf0cRYVLgkSPKgo4vsl7o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;424&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;파이프라인 스크립트 작성하기 이전에 git hub 자격증명을 추가해주세요.&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;940&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VFRMW/btsDXEp8OjV/D86ROQrJosQmx4dvsJaWA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VFRMW/btsDXEp8OjV/D86ROQrJosQmx4dvsJaWA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VFRMW/btsDXEp8OjV/D86ROQrJosQmx4dvsJaWA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVFRMW%2FbtsDXEp8OjV%2FD86ROQrJosQmx4dvsJaWA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;382&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Jenkins 파이프라인 스크립트 작성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. git hub 빌드 &amp;rarr; 2. 프로젝트 파일 jar 파일로 출력 &amp;rarr; 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;940&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V3514/btsDXabLVBY/027YAWNvrsjLI8xT9D6YOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V3514/btsDXabLVBY/027YAWNvrsjLI8xT9D6YOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V3514/btsDXabLVBY/027YAWNvrsjLI8xT9D6YOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV3514%2FbtsDXabLVBY%2F027YAWNvrsjLI8xT9D6YOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;465&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 빌드 &amp;rarr; 2. 빌드 성공&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;940&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnwl1u/btsDXC0blB2/aotUPI5ZW61WwxO1ochpo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnwl1u/btsDXC0blB2/aotUPI5ZW61WwxO1ochpo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnwl1u/btsDXC0blB2/aotUPI5ZW61WwxO1ochpo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnwl1u%2FbtsDXC0blB2%2FaotUPI5ZW61WwxO1ochpo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;290&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이제 CI/CD 구축이 완료되었습니다.&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;678&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg6xAt/btsDRpBKKWv/H2RK0JO3v5imnSOqaCDg4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg6xAt/btsDRpBKKWv/H2RK0JO3v5imnSOqaCDg4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg6xAt/btsDRpBKKWv/H2RK0JO3v5imnSOqaCDg4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg6xAt%2FbtsDRpBKKWv%2FH2RK0JO3v5imnSOqaCDg4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;197&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;지금까지 AWS EC2 서비스를 통해 CI/CD를 구축하는 법에 대해 알아보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 글을 보시는 여러 개발자분들께 도움이 되었길 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드</category>
      <category>AWSEC2</category>
      <category>CICD</category>
      <category>CICD구축</category>
      <category>CICD파이프라인</category>
      <category>jenkins</category>
      <category>사이버이메지네이션</category>
      <author>CyberI</author>
      <guid isPermaLink="true">https://cyberx.tistory.com/313</guid>
      <comments>https://cyberx.tistory.com/313#entry313comment</comments>
      <pubDate>Thu, 25 Jan 2024 09:56:10 +0900</pubDate>
    </item>
  </channel>
</rss>