<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>haeunnoh.log</title>
        <link>https://velog.io/</link>
        <description>Tistory로 옮기게 되었습니다. @haeunnohh</description>
        <lastBuildDate>Sun, 16 Feb 2025 09:04:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>haeunnoh.log</title>
            <url>https://velog.velcdn.com/images/1109_haeun/profile/d5977682-5eb8-4f9c-8c6f-f26d85495763/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. haeunnoh.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/1109_haeun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Tistory로 옮기게 되었습니다.]]></title>
            <link>https://velog.io/@1109_haeun/Tistory%EB%A1%9C-%EC%98%AE%EA%B8%B0%EA%B2%8C-%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@1109_haeun/Tistory%EB%A1%9C-%EC%98%AE%EA%B8%B0%EA%B2%8C-%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 16 Feb 2025 09:04:12 GMT</pubDate>
            <description><![CDATA[<p><a href="https://haeunnohh.tistory.com/1">https://haeunnohh.tistory.com/1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[생각나누기] 개발자의 평생공부에 대한 고찰]]></title>
            <link>https://velog.io/@1109_haeun/%EC%83%9D%EA%B0%81%EB%82%98%EB%88%84%EA%B8%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%8F%89%EC%83%9D%EA%B3%B5%EB%B6%80%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@1109_haeun/%EC%83%9D%EA%B0%81%EB%82%98%EB%88%84%EA%B8%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%8F%89%EC%83%9D%EA%B3%B5%EB%B6%80%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Tue, 17 Sep 2024 15:43:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>입사한지 한 달차도 안 된 신입이 &#39;개발자의 평생공부&#39;라는 글을 읽고 한 생각을 공유하고자 작성한 글입니다. 편하게 읽어주세요. </p>
</blockquote>
<p>추석에 &#39;개발자의 평생공부&#39;라는 제목의 글을 읽게 되었다.
<span style="color: gray; font-weight: 400; font-size: 14px;">링크는 아래에 걸어두겠습니다!</span></p>
<p>읽게 된 경위가 무엇이냐 하면 추석에 열심히 공부한다 해놓고 유데미 온보딩 강의만 열심히(..?) 들었기에 뭐라도 공부하는 척이라도 해야겠다는 마음이었다.
마치 중학생때 시험공부는 안 하고 동기부여 영상만 봤던 나처럼</p>
<ul>
<li>&#39;개발자의 평생공부&#39;에 나온 글들은 <strong>인용글</strong>로 처리하여 구분하였습니다.</li>
</ul>
<br>

<hr>
<h2 id="실력이-무엇인가">실력이 무엇인가?</h2>
<blockquote>
<p>: 지식의 총합이 아니다. 고통의 총합이다.</p>
</blockquote>
<p>실력은 지식의 총합이 아니라 고통의 총합이라는 말을 듣자마자 이 글은 정독해야겠다는 생각을 했다. 그만큼 위의 말에 백번 천번 공감한다.</p>
<p>그러기 위해서는 우선 <strong>‘지식’</strong>이라는 개념이 나에게 어떻게 정의되고 있는지를 써야할 것 같다.
나에게 있어 지식이란 어디선가 본 것을 읽거나 기록하거나 듣거나 코드로 작성하는 것으로 습득하는 것이다.</p>
<p>하지만 위의 상황들 중 공통점이 있다.
어떠한 브레이크가 되어줄 상황이 없는 것이다.</p>
<p>내가 습득한 것이 맞는지, 잘 동작하지 않는다던가 하는 습득한 것을 되돌아볼만한 경험이 부족하다. 어떠한 것의 리마인드가 있을 때 그 습득력은 몇 배로 불어난다.</p>
<p>당장 나만 하더라도 프로젝트를 하며 크게 실패하고 고민해보았던 부분은 뇌리에 깊숙히 박혔지만 순탄히 기능 구현이 잘 되었을 때는 그려러니 하고 다음에 똑같은 기능을 개발할 때 즈음에는 다 까먹었던 것 같다.</p>
<p>나의 상황에 대입하여 봤을 때 어느정도 고통의 총합이 실력이 될 수 있지 않는가.
이 글을 보는 여러분도 분명 크게 당황했을 때의 기억이 더욱 선명할 것이다.</p>
<br>

<h3 id="겉핥기가-공부로서-부족한-이유">겉핥기가 공부로서 부족한 이유</h3>
<blockquote>
<p>파편적인 지식은 파편적인 태도만으로 충분하다
트렌드에 필요한 것은 가벼운 눈팅이지 공부가 아니다.
공부는 <strong>‘본질’</strong>에 다가서려는 노력이다.</p>
</blockquote>
<p>이 부분이 내 막힌 혈자리를 뚫어주었다!</p>
<p>동시에 왜 이제껏 파편적인 것들에만 접근하였는가 라는 생각이 들었다.</p>
<p><strong>’트렌드’</strong> 라는 이름은 굉장히 세련되어 보인다.
’트렌드’에 민감한 사람은 정말 실력이 좋고 발전 가능성이 무궁무진할 것 같은 느낌이다.
적어도 나는 그렇게 생각하는 사람이었다.
<span style="color: gray; font-weight: 400; font-size: 14px;">(물론 ‘본질’을 잘 알면서 ‘트렌드’를 잘 쫒아가는 분들에게는 해당사항이 없는 이야기이다.)</span></p>
<p> 다른 친구들이 ‘채팅 기능’이 들어간 서비스를 개발하기 시작했다고 하면 ‘나도 그런 기능을 가진 서비스를 개발해야하는 것은 아닌가?’ 라는 생각이 들었다.</p>
<p>하지만 그러한 트렌드를 쫒다 중요한 본질을 놓치고 있었다.</p>
<ul>
<li>정말 기본적인 개발 공부를 하는 방법</li>
<li>코드를 읽는 방법</li>
<li>클린 코딩을 하는 방법</li>
<li>효율적인 쿼리를 짜는 방법</li>
<li>수많은 사용자들이 발생시킬 예외를 처리하는 방법 등등</li>
</ul>
<p>신입 개발자로서 앞으로 몇 십년동안 걸어갈 길의 초석을 쌓은 것이 아닌 열심히 공부한다는 모습에만 집중하고 있었던 것이 아닌가 하는 생각이 든다.</p>
<p>본질적인 부분을 보았을 때 그 트리가 한 눈에 보이면서 결국에는 파편적인 부분이 자연스레 따라올 수 있었을텐데,
소위 말하는 효율적인 공부를 할 수 있었을텐데 하는 생각이 든다.</p>
<br>

<hr>
<h2 id="어떻게-공부해야-하나요">어떻게 공부해야 하나요?</h2>
<blockquote>
<ol>
<li>지금 다니고 있는 회사에서 하는 일을 잘하기 위해서 노력하는 것이 가장 좋은 공부다</li>
<li>회사에서 하는 일과 개인적으로 공부하는 내용을 최대한 근접시키기 위해서 노력하라.</li>
<li>새로운 기술을 익히는 최선의 방법은 스스로 문제를 정의한 다음, 새로운 기술을 이용해서 그 문제를 풀어보는 것이다. 책을 읽거나 동영상을 보는 것은 그보다 하위수준의 방법이다.</li>
<li>신기술을 좇는 메뚜기가 되지 말라.</li>
<li>모든 것을 알아야 한다는 강박을 버려라. 미리 획득하는 지식의 99%는 무용지물이다. 필요할 때 필요한 기술을 익힐 수 있는 것이 능력이다. 그 능력을 키워라.</li>
<li>이상한 나라의 앨리스에 나오는 토끼굴을 피하라. 카테고리이론을 알아야 함수형 언어를 쓸 수 있는 게 아니고, 선형대수학을 공부해야 머신러닝을 할 수 있는 게 아니다. 토끼굴에 빠져서 한없이 들어가다보면 비본질적인 공부에 시간을 허비하게 된다.</li>
<li>겉만 핥는 것은 경박하지만 토끼굴에 빠지는 것은 우매하다. 둘 사이의 적당한 지점에서 균형을 잡는 것이 개발자의 능력이다.</li>
<li>머리에 들어오지 않는 어려운 개념이나 용어는 자투리 시간을 이용해서 반복적으로 읽고 암기하라. 나중에 큰 그림을 공부할 때 도움이 된다.</li>
<li>항상 겸손해야 하지만 동시에 자긍심을 가져라. 그대가 지금 작성한 코드, 지금 읽은 책, 지금 공부한 내용을 그대보다 잘 아는 사람은 지구상에 없다. 모든 걸 알고 있는 것처럼 보이는 다름 사람들도 그대와 마찬가지로 불안해하고, 위축되고, 두려워하면서 살아가고 있다. 자긍심이란 그런 타인을 돕고자 하는 마음가짐의 다른 이름이다.</li>
<li>혼자 하지말고 함께 공부하라.</li>
</ol>
</blockquote>
<p><span style="color: gray; font-weight: 400; font-size: 14px;">!위의 내용을 다루며 중복되는 내용들 또는 저로서 크게 나눌 점이 없다고 느낀 내용들에 대해서는 다루지 않았습니다!</span></p>
<br>

<h3 id="회사의-기술을-파악하고-응용하자">회사의 기술을 파악하고 응용하자</h3>
<blockquote>
<ol>
<li>지금 다니고 있는 회사에서 하는 일을 잘하기 위해서 노력하는 것이 가장 좋은 공부다</li>
</ol>
</blockquote>
<p>입사 전에는 일을 잘 하는 사람은 다른 회사에 가서도 바로 일을 잘 하게 될 줄 알았다.
어느정도는 맞는 말일 것이다.
시니어 개발자분들은 기본적인 CS, 알고리즘, 자료구조에 대한 지식과 코드를 읽는 방법을 아실테니 <strong>‘적응’</strong>이 확연히 빠르시리라 생각된다.</p>
<p>하지만 각 회사에서 추구하는 바가 전부 다르기에 컨벤션, 코드 구조, 언어, 프레임워크, 라이브러리 등등 회사의 핏에 맞게 개발을 해야한다.</p>
<p>회사에서는 나 혼자 쓰는 코드가 아니기 때문에 다른 팀원분들도 코드를 보고 리뷰를 남기고 추후 리팩토링이 들어가기 때문에 동일한 구조로 코드를 짜야한다.</p>
<p>따라서 그 회사에서 일을 잘 하기 위해서는 <strong>회사가 흘러가던 방식에 내가 적응하여 코드를 작성할 수 있는 역량</strong>이 동반되어야 한다고 생각한다.
동시에 회사가 가지고 있는 기술을 익히고 응용할 줄 알아야 한다.</p>
<p>지금의 나는 그 구조와 기술을 파악하는 능력을 길러야 한다는 생각이 정말 분명히 든다.
있는 것을 활용하지 않고 없던 것을 만들어내는 것만큼 불필요하고 비효율적인 시간 활용이 없을거라 생각한다.</p>
<br>

<h3 id="지금-하는-자기계발이-본업의-능률에-도움이-되는가">지금 하는 자기계발이 본업의 능률에 도움이 되는가?</h3>
<blockquote>
<ol start="2">
<li>회사에서 하는 일과 개인적으로 공부하는 내용을 최대한 근접시키기 위해서 노력하라.</li>
</ol>
</blockquote>
<p>위와 일맥상통하는 말인듯 싶지만 조금 다르다.
근무 시간 이외에 자기계발을 하는 시간에 조금 더 맞는 조언인듯 싶다.</p>
<p>같은 팀원분이 한 번 질문을 해주신 적이 있다.
<strong>’프로게이머가 수능공부를 한다고 해서 본업을 더 잘 하게 될까요?’</strong></p>
<p>막연히 어떠한 분야에서든 자투리 시간을 투자하여 자기계발을 한다면 공부라고 생각했었는데 굉장히 신선한 충격이었다.</p>
<p>아직 회사에 대해서 아무것도 모르는 신입인 내가 자기계발해야할 것은 회사의 코드 이해겠지만 더 나아간다면 자기계발하는 내용이 진정으로 내게 필요한 것인가를 계속해서 되물어야겠다.</p>
<p>관련된 취미를 재미삼아도 괜찮겠다는 생각이 든다.
학창시절 스터디를 여는 것도, 참여하는 것도 정말 좋아했었는데 스터디를 해볼까싶기도 하고
조금 더 여유가 생긴다면 사이드 프로젝트를 운영해보고 싶다.</p>
<br>

<h3 id="지금-필요하지-않는-지식을-미리-찾는-것은-시간-낭비일-수-있다">지금 필요하지 않는 지식을 미리 찾는 것은 시간 낭비일 수 있다</h3>
<blockquote>
<ol start="5">
<li>모든 것을 알아야 한다는 강박을 버려라. 미리 획득하는 지식의 99%는 무용지물이다. 필요할 때 필요한 기술을 익힐 수 있는 것이 능력이다. 그 능력을 키워라.</li>
</ol>
</blockquote>
<p>이 얘기의 주인공이 바로 나다.</p>
<p>어떠한 것을 시작하기 위해서는 <strong>1부터 100까지 전부</strong> 알고 시작해야 직성이 풀렸다.
하지만 그렇게 시작했다고 모든 것을 다 완벽하게 하지 못했다.
오히려 이미 공부했던 것을 또 찾아보느라 시간이 두 배로 걸리는 사태가 벌어졌다.</p>
<p>그러다보니 필요할 때 그 필요한 지식만 쏙 골라 습득한 후 응용하는 친구들이 보이기 시작했다.
분명 똑같은 시간이 주어졌는데도 그 친구들의 작업속도는 가히 압도적이었다.
하루가 48시간인가? 하는 착각을 일으킬만큼 필요한 것과 필요없는 것을 구분하여 시간을 활용했다.</p>
<p>개발자는 구글링도 실력이라는 말을 들은적이 있다.
친구와 분명 똑같은 것을 검색했는데도 결과값으로 받아들이는 것이 달랐던 적이 많다.</p>
<p><strong>’이거 어떻게 검색했어?’</strong> 라고 물으면
<strong>’그냥 영어로 이렇게 검색했는데?’</strong></p>
<p>라며 올바른 결과값을 도출하는 모습을 보였다.</p>
<p>문제의 <strong>핵심</strong>이 되는 <strong>키워드</strong>를 뽑아 영어로 구글링을 하고
그렇게 나오는 수백만개의 포스트 중 해결책이 되는 것을 찾는 연습을 해야한다고 생각한다.</p>
<p>지금처럼 에러가 나오면 전부 복사붙여넣기하는 상황을 벗어나기 위해서라도 말이다.</p>
<br>

<h3 id="정확히-파악하려다-본질을-잊지-말자">정확히 파악하려다 본질을 잊지 말자</h3>
<blockquote>
<ol start="6">
<li>이상한 나라의 앨리스에 나오는 토끼굴을 피하라. 카테고리이론을 알아야 함수형 언어를 쓸 수 있는 게 아니고, 선형대수학을 공부해야 머신러닝을 할 수 있는 게 아니다. 토끼굴에 빠져서 한없이 들어가다보면 비본질적인 공부에 시간을 허비하게 된다.</li>
</ol>
</blockquote>
<p>구글링에서 핵심만 쏙 뽑아 적용하는 친구들이 대단하다고 느꼈던 두 번째 이유다.
무언가 해결을 하기 위해서 구글링을 시작하면 <strong>모르는 원리</strong>가 등장한다.
이 원리를 모르면 도저히 이 글을 완벽히 이해할 수 없겠다는 판단이 들자마자 크롬의 새 창을 띄운다.
그렇게 내 크롬 창은 디폴트 개수가 10이 되었다🥲</p>
<p>절대적인 지식의 부족이 있다는 것을 부정하는 것은 아니지만 토끼굴의 정도가 너무 지나치다.
결국 그렇게 토끼굴에 깊숙히 들어가다보면 본질이었던 에러 해결 창은 조그마해 찾기도 힘들어진다.</p>
<p>일차원적으로 지식은 계속해서 습득하되 문제를 해결할 때의 그 본질을 잊지 말고 <strong>원인</strong>을 찾아보자.</p>
<p>위의 글에서도 </p>
<blockquote>
<p>‘겉만 핥는 것은 경박하지만 토끼굴에 빠지는 것은 우매하다.’</p>
</blockquote>
<p>라는 말을 한다.
겉만 핥는 것은 당시에는 해결이 된듯 싶어도 결국 문제 해결의 원리는 알지 못하고 넘어갈 수 있다.
그렇다고 토끼굴에 빠지는 것은 파편만 수집하다 문제를 잊어버릴 수 있다.</p>
<p>그 중간을 찾아 <strong>균형</strong>을 유지하는 능력 또한 인정받는 개발자의 태도가 아닐까 생각된다.</p>
<br>

<h3 id="나는-신뢰할-수-있는-팀원인가">나는 신뢰할 수 있는 팀원인가?</h3>
<blockquote>
<ol start="9">
<li>항상 겸손해야 하지만 동시에 자긍심을 가져라. 그대가 지금 작성한 코드, 지금 읽은 책, 지금 공부한 내용을 그대보다 잘 아는 사람은 지구상에 없다. 모든 걸 알고 있는 것처럼 보이는 다름 사람들도 그대와 마찬가지로 불안해하고, 위축되고, 두려워하면서 살아가고 있다. 자긍심이란 그런 타인을 돕고자 하는 마음가짐의 다른 이름이다.</li>
</ol>
</blockquote>
<p>예전부터 내가 한 것을 뽐내는 것에 굉장히 조심스러워했다.
칭찬을 받아도 <strong>‘에이 아니에요~ 다른 분이 잘 하신 거죠.’</strong> 와 같이 절대 내 공이라고 먼저 말하지 않았다.</p>
<p>그런 행동을 반복하다보니 문득 <strong>‘주변 사람들이 나를 신뢰할 만한 사람으로 생각할까?’</strong> 라는 의문이 들었다.
동시에 <strong>‘회사에서도 똑같이 행동하면 팀원들이 나를 믿고 일을 맡길 수 있을까? 자신의 본업에 지장이 갈 만큼 신경이 쓰이지 않을 수 있을까?’</strong> 하는 생각이 들었다.</p>
<p>학교에서라면 모를까 회사는 효율적으로 최대한의 이윤을 내야하는 조직이기에 능률을 떨어뜨리는 팀원을 반길리 없었다.</p>
<p>잘한 점은 잘했다고 표현하되 못 한 점은 확실히 보완해야겠다는 생각이 든다.
하지만 내가 짠 코드에 자긍심을 가질만큼 코드를 짤 때 생각하며 구조를 완벽히 파악하면서 코드를 짤 수 있도록 노력해야겠다.
나조차도 알아보기 힘든 더러운 코드에는 자긍심을 느낄래야 느낄 수도 없을테니 말이다.</p>
<br>

<hr>
<h2 id="개발-재밌나요">개발 재밌나요?</h2>
<blockquote>
<p>가슴에 손을 얹고 스스로 질문해보기 바란다.
공부가 재밌는가?
정말 재밌는가?
새로운 기술을 익히고, 키보드를 두드리고, 결과를 확인하고, 친구들과 얘기하는 모든 경험이 그대를 정말 행복하게 만드는가?
이 질문에 대한 대답이 예스라면 그 예스의 강도만큼 그대의 미래는 성공이 보장되어 있는 것이다.
그러므로 개발자는 미래에 대해 불안해할 필요가 없다.
미래의 성공은 예스라는 작은 변수의 함수이기 때문이다.
그 변수는 개발자 자신의 손에 놓여 있다.</p>
</blockquote>
<p>회사 첫 날에는 긴장이 돼서 미치는 줄 알았다.
개발 세팅도 헤메는 나 자신에 진지하게 이 길이 맞는지 고민도 했다.</p>
<p>하지만 온보딩을 진행하면서 무언가 되지는 않고 있었지만(ㅎ) 진심으로 개발에 재미를 느꼈다.
학교에서 늘 하던거 하다가 회사라는 아예 새로운 환경에 놓이니 개발 도파민이 폭발이라도 한건지 계속 회사에 남아있으면서 개발하고 싶었고 매일매일 회사 가는 아침이 설레였다.
1년 뒤의 내가 이 글을 다시 봤을 때 경악을 할지언정 현재는 정말 즐겁고 개발이 재밌다고 느끼고 있다.</p>
<p>회사에서 개발한 프레임워크의 구조를 파악하고 개발되어 있는 컴포넌트를 찬찬히 뜯어봐 알맞게 적용했을 때의 행복감이 정말 이루말할 수 없다.</p>
<p>그럼에도 불안한 이유는 <strong>속도가 너무 느리다는 것, 기초 지식이 너무나도 부족하다는 것.</strong></p>
<p>하지만 위의 글은 </p>
<blockquote>
<p>‘그 예스의 강도만큼 그대의 미래는 성공이 보장되어 있는 것이다.’ </p>
</blockquote>
<p>라는 말을 해주고 있다.
이 한 마디가 계속 노력할 수 있는 원동력을 준 것 같다.</p>
<p>온보딩 프로젝트 개발이 끝나고 팀원분께 온보딩 프로젝트를 더 해보고 싶다고 한 뒤 개발을 했을 때 옆자리의 팀원분과 얘기를 짤막히 나눈 적이 있다.</p>
<blockquote>
<p>’팀원분: (프로젝트 재밌냐는 듯한 뉘앙스로 질문하셨습니다.)
나: 네! 재밌어요. 온보딩때는 시간 제한이 걸려있어서 기능 구현에만 집중을 했었거든요. 그래서 구조를 뜯어보면서 할 수 없어서 재미가 떨어졌었는데 이제는 시간 제한도 없어서 하나하나 파악하면서 했더니 더 재밌는 것 같아요.’
팀원분: 저라면 이제 다 끝나서 흥미가 떨어질 것 같은데 진짜 찐 개발자시네요.’</p>
</blockquote>
<p>그 말을 들었을 때 내가 개발하는 것을 진심으로 좋아하는구나 싶었다.
세상을 살면서 가장 중요한 가치를 질문으로 받았을 때 나는 <strong>‘좋아하는 것을 잘하게 되어서 꾸준히 할 수 있게 되는 것’</strong> 이라고 답변한 적이 있는데 위의 경험에서 확립된 것 같다.</p>
<br>

<hr>
<p>갑자기 이런 류의 글을 써보고 싶어서 썼는데 확실히 쓰고 싶었던 주제다보니 분량이 많은데도 2시간정도만에 술술 써진 것 같다.</p>
<p>짧은 시간만에 써진 글이라 어색할 수도 있지만 이런 주제로 얘기하는 것이 소소한 재미이기에 올려보게 되었다.</p>
<p>이런 깨달음을 얻고 글을 쓰고 공유를 해도 바로 내 생활에 적용이 되는 것은 쉽지 않겠지만 한 번 고찰해보고 글로 옮기는 행동으로 내 인생의 조그마한 마일스톤이 생겼을 것이라 생각한다.</p>
<br>

<hr>
<h3 id="reference">reference</h3>
<ul>
<li><a href="https://zdnet.co.kr/view/?no=20170616090644">개발자의 평생공부</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Review] 졸업 전시 회고록]]></title>
            <link>https://velog.io/@1109_haeun/%ED%9A%8C%EA%B3%A0-2up348me</link>
            <guid>https://velog.io/@1109_haeun/%ED%9A%8C%EA%B3%A0-2up348me</guid>
            <pubDate>Wed, 26 Jun 2024 16:38:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>저는 미림마이스터고에서 백엔드 개발자가 되기 위해 공부하고 있는 고등학교 3학년 노하은입니다.
고등학교 졸업 전시를 어떻게 준비하고 진행했으며 무엇을 느꼈는지를 나누기 위해 이 글을 작성합니다. </p>
</blockquote>
<br>

<h2 id="🤔-고등학생이-졸업-전시를">🤔 고등학생이 졸업 전시를?</h2>
<p>제가 현재 재학중인 미림마이스터고는 <strong>뉴미디어 IT쇼</strong>라는 졸업 전시를 고등학교 3학년 6월달에 진행합니다.</p>
<blockquote>
<p>⭐ ** 뉴미디어 IT쇼란?**
학생들끼리 팀을 꾸려 제작한 프로젝트를 3일간 전시하는 미림마이스터고의 졸업 전시회입니다.</p>
</blockquote>
<p>일반인뿐만 아니라 회사 관계자분들도 방문하시며 도슨트 학생들이 프로젝트를 설명해드리는 명실상부 학교의 대표 행사입니다.</p>
<p>입학 설명회 때에 맞춰 소개되기도 하여 학생들도 나름의 책임감과 자부심을 가지고 진심으로 참여하고 함께 만들어갑니다. </p>
<br>

<h2 id="⌛-고등학교-마지막-졸업-전시인데">⌛ 고등학교 마지막 졸업 전시인데..</h2>
<p>재학중이었던 2년간 선배님들의 졸업전시를 계속 지켜봐왔습니다.
매년 성대하게 치뤄지는 학교의 대표 행사였던만큼 저 또한 흥미롭게 선배님들의 작품을 감상하고 즐겼으며 반드시 후회없는 전시를 만들고자 하였습니다. </p>
<p>사실 전 이미 2학년때 IT쇼에 작품을 출품한 경험이 있었습니다.
작년 IT쇼가 끝나고 제게 들었던 감정은 단순했습니다.</p>
<blockquote>
<p><em>아쉽다. 하지만 재미있었다.</em></p>
</blockquote>
<p>처음으로 나의 서비스가 실제 사용자들에게 사용되는 것을 볼 수 있었던 경험이었으며 고등학생이 이런 기회를 얻기란 쉽지 않다고 생각했습니다.</p>
<p>하지만 동시에 개발에만 너무 몰두하느라 실제 전시 기간에도 다른 작품들을 체험해보지 못하였습니다. </p>
<p>따라서 내년에는 반드시 내 최대한의 역량을 다해 최대한의 작품을 출품하고 최대한의 퀄리티로 나도 즐기고 사용자분들도 즐길 수 있는 전시를 만들고자 마음먹었습니다.</p>
<p><del>그렇게 저는 무려 4개의 작품을 출품하였고 그 중 3개의 작품에서 팀장을 맡게되는 일정을 소화하게 됩니다.</del></p>
<br>

<h2 id="👩💻-무슨-프로젝트를-개발하셨나요">👩‍💻 무슨 프로젝트를 개발하셨나요?</h2>
<p>총 4개의 작품에 참여하였고 간단한 프로젝트 소개와 짤막한 회고를 작성하려 합니다.
저는 모든 작품에서 백엔드를 맡았습니다. </p>
<h3 id="💟-workey---🐈⬛깃허브">💟 Workey - <a href="https://github.com/2024Workey">🐈‍⬛깃허브</a></h3>
<blockquote>
<p>호주 멜버른 글로벌 인턴쉽(3주)에서 개발한 작품으로, 워라밸을 효과적으로 관리할 수 있는 직장인들을 위한 앱 서비스 
✅ Express.js, Sequelize, MySQL, EC2, RDS</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/9b49a159-ebe2-4709-ba03-72f6d27daf9c/image.png" alt=""></p>
<ul>
<li>타임바를 통한 근무 시간 시각화</li>
<li>하루 한 개의 질문에 대한 일기 작성</li>
<li>회사별 워라밸 랭킹</li>
</ul>
<p>workey는 기능을 구현한 시간보다 오류를 발견하고 고치는 시간이 배로 들은 프로젝트입니다.</p>
<p>인턴십 기간 동안 빠른 기능 구현에 집중한 나머지 코드의 질이 떨어졌던 것이 원인이었습니다.
모두가 유지보수를 외치는 이유를 알 수 있었고 또 막상 코드를 짤 때는 이 정도면 괜찮다는 생각이 드는데 그런 함정에 쉽게 빠져버려 레거시 코드를 왕창 만들어내는 게 아닌가하는 생각이 듭니다.</p>
<br>

<h3 id="💟-고민---🐈⬛깃허브">💟 고민 - <a href="https://github.com/2024-Go-Mean">🐈‍⬛깃허브</a></h3>
<blockquote>
<p>ChatGPT OpenAI가 고민을 듣고 답변해주는 고민 해결 웹 서비스
✅ Golang, MongoDB, EC2</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/a3b56cae-6582-4f2b-8eee-54949fad1244/image.png" alt=""></p>
<ul>
<li>카테고리별 고민 작성</li>
<li>ChatGPT의 답변</li>
<li>고민에 대한 댓글 작성</li>
<li>카테고리별 작성된 답변 퍼센트 시각화</li>
</ul>
<p>이 프로젝트를 구상하게 된 계기가 참 단순한데 &#39;졸업전시인데 한 작품은 디자인에 3D를 넣고 싶다!&#39; 였습니다.</p>
<p>그러다보니 프로젝트의 기능이 거진 CRUD만 있게 되어서 &#39;이왕 단순한걸 할바에는 전혀 다루지 않았던 새로운 언어와 DB를 써보자!&#39; 해서 냅다 Golang과 MongoDB를 사용하자는 결론을 내렸습니다 하하
결론적으로는 프로젝트 완성을 했지만 이제보니 막무가내로 기술 스택을 정한게 아닌가 하는 생각이 드네요.</p>
<p>단순한 작품이지만 감사하게도 졸업 전시의 AI파트에서 해당 작품이 대표 작품으로 선정되어 기업 관계자분들께 소개되기도 했습니다.</p>
<p>여담으로 ChatGPT OpenAI를 가져와 답변을 받았을 때 특유의 딱딱한 구성과 말투가 거슬렸습니다.</p>
<blockquote>
<p>&quot;고민을 털어놓는 사람들에게는 좀 더 부드러운 말투와 형식없는 구성이어야 하지 않을까?&quot;</p>
</blockquote>
<p>하는 생각이 들어 프롬프트를 제가 추가로 작성하였는데 그 부분을 기업관계자분께서 &quot;어떻게 이렇게 부드러운 말투로 나오게 하셨어요?&quot; 라고 칭찬을 들어서 굉장히 기분이 좋았습니다😁</p>
<br>

<h3 id="💟-이-구역의-댄싱-퀸은-나야-나---🐈⬛깃허브">💟 이 구역의 댄싱 퀸은 나야 나 - <a href="https://github.com/MSG-Mirim-Study-Group/2024-Egudanna-Server">🐈‍⬛깃허브</a></h3>
<blockquote>
<p>학교의 전공동아리에서 개발한 작품으로, 숏폼 챌린지 영상을 촬영하면 메일로 영상을 보내주는 숏폼 웹 서비스
✅ Spring Boot 3.0, MariaDB, EC2, S3</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/15bc41ed-9f7d-4c03-8da6-861d0f5cd989/image.png" alt=""></p>
<ul>
<li>촬영한 영상 S3에 저장</li>
<li>Gmail SMTP로 영상 url 전송</li>
<li>쇼츠 형태로 촬영된 챌린지 영상 시청 및 좋아요, 댓글 기능</li>
<li>난이도, 카테고리별 필터링</li>
</ul>
<p>작년 동아리에서 출품했던 행성 MBTI 웹사이트가 사용자분들께 큰 인기를 받았었기에 이번에도 사용자분들이 즐길 수 있는 서비스를 만들고 싶었고 숏폼 웹 서비스를 기획하였습니다.</p>
<p>촬영자 중에서는 체육 선생님도 계셨는데 마라탕후루 춤을 기깔나게 춰주셔서 진짜 배꼽 빠지게 웃었습니다. 
그 영상은 당당히 좋아요 수 1등을 차지했고 체육 선생님 왈 자기 자신을 다시 보고 싶지 않다고 하셨습니다😂</p>
<p>재미있던 것과 별개로 개발적인 부분에서 굉장히 발전할 수 있었던 프로젝트입니다. 특히나 Cloud(AWS)를 사용하는 법을 배울 수 있었습니다. 
Spring Boot로 API를 만드는 것뿐만 아니라 S3에 영상을 업로드하고 서버를 EC2로 배포하는 등 AWS를 적극 활용해보았습니다.</p>
<br>

<h3 id="💟-pinny---🐈⬛깃허브">💟 Pinny - <a href="https://github.com/pinny2024">🐈‍⬛깃허브</a></h3>
<blockquote>
<p>사회 초년생을 위한 돈 관리 및 소비 패턴 개선 앱 서비스
✅ Spring Boot 3.0, MySQL</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/feb4f20d-9f34-48cc-9113-f6316ad465d7/image.png" alt=""></p>
<ul>
<li>저축을 통한 돈 모으기 퀘스트 달성</li>
<li>나의 과소비 지수 확인</li>
<li>예산을 세우고 일주일 계획을 체크하며 소비 패턴 확인</li>
</ul>
<p>부끄럽지만 이 프로젝트를 통해서 CRUD API 외의 API가 필요하다는 사실을 깨달았습니다.</p>
<p><em>?? 이게 무슨 말이야</em></p>
<p>황당하겠지만 저는 서버에서 CRUD API만 만들어주면 프론트에서 여러 작업들을 대신 해주는줄 알았습니다.
&quot;Workey&quot; 프로젝트에서 풀스택으로 개발했던 탓에 CRUD API만을 가지고 프론트에서 기능을 구현하는 것이 당연하게 여겨져 발생한 이슈였습니다.</p>
<p><del>덕분에 프론트분들의 수정사항을 하루에 3개씩 받게 되었습니다 하하</del></p>
<p>졸업 전시가 얼마 남지 않은 시점에 API를 대거 추가하는 작업은 힘들었지만 덕분에 repository, service, controller의 역할을 명확히 이해할 수 있었습니다.</p>
<br>

<h2 id="🎭-소프트-스킬-하드-스킬">🎭 소프트 스킬? 하드 스킬?</h2>
<blockquote>
<p>개발은 컴퓨터를 대하는 것이지만 프로젝트는 사람을 대하는 것이라고 생각합니다.</p>
</blockquote>
<p><em>개발자라면 개발을 잘 하는 하드 스킬이 더 중요한 거 아닌가?</em>
하는 생각을 가지고 있었지만 그에 못지 않게 소프트 스킬도 중요하다는 걸 깨달았습니다.</p>
<h3 id="🧭-나의-진행상황을-공유해라">🧭 나의 진행상황을 공유해라</h3>
<p>pinny 프로젝트를 진행하면서 서버 코드가 변경된 부분을 프론트 친구가 몰라 원인 모를 에러가 발생하여 머리를 싸맨 상황이 발생했습니다.
2명과는 같은 기숙사를 사용하고 있어 변경사항을 바로바로 공유하였는데 다른 1명에게는 미처 공유를 하지 않아 벌어진 일이었습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/1588673e-1e93-4ed6-accf-22fac4763cb1/image.png" alt=""></p>
<p>이후 작은 것도 카톡방에서 나누며 서로의 변경사항을 지속적으로 업데이트하며 불필요한 작업을 줄여나갔습니다.</p>
<br>

<h3 id="💬-요청받은-기능은-내가-이해한-것이-맞는지-되물어보자">💬 요청받은 기능은 내가 이해한 것이 맞는지 되물어보자</h3>
<p>주로 프론트 친구가 <em>~~한 API 추가해주세요</em> 라는 요청을 했는데 그 때마다 제가 이해한 것이 맞는지 항상 되물어보았습니다.</p>
<p>멋대로 이해하고 열심히 기능을 구현했는데 알고보니 다른 뜻이었다는 불상사를 막을 수 있어 프로젝트가 원활하게 진행이 된 것 같습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/f314cd3f-a95c-4d30-ab72-e8755bb00f82/image.png" alt=""></p>
<br>

<h3 id="📜-문서를-주기적으로-업데이트하자">📜 문서를 주기적으로 업데이트하자</h3>
<p>&quot;이 구역의 댄싱 퀸은 나야 나&quot; 프로젝트에서 API 명세서를 업데이트하지 않아 프론트 친구의 작업이 많이 딜레이되었습니다.</p>
<p>올바른 request와 response가 주어져야 에러의 원인 파악이 빨랐을텐데 단순한 작업을 놓쳐 많은 손실이 발생한 것 같아 지금도 팀원들에게 굉장히 미안합니다..</p>
<br>

<h2 id="🖐️-마치며">🖐️ 마치며</h2>
<p>분량 조절에 실패하였습니다..
더 담고 싶은 말들이 많았으나 넘치는 분량 이슈로 미처 담지 못한 내용이 있어 아쉽습니다.</p>
<p>작년 선배들의 작품을 보며 <em>나는 무조건 3개 이상의 작품을 출품해야지</em> 하는 생각을 했었는데 그에 상응하듯 4개의 작품을 성공적으로 출품하였습니다. </p>
<p>저의 3년이 이것으로 모두 보여지지는 않겠지만,
다른 엄청난 분들의 프로젝트에 비해서는 소박하다고 느껴질 수도 있겠지만,</p>
<p>고등학생의 신분으로 졸업 전시를 통해 사용자경험을 할 수 있었다는 것,
한계까지 몰아붙여본 경험,
스스로의 부족함을 가감없이 돌아볼 수 있었던 경험</p>
<p>순탄하지 않았던 모든 과정에서 여러 경험을 할 수 있었음에 감사했습니다.
전시 3일간 수많은 분들께 아낌없는 칭찬과 격려를 받을 수 있어 행복했고 보람을 느꼈습니다.</p>
<p>이 경험을 통해 이전보다 성장한 제가 있길 바랍니다.</p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[React][TypeScript] React+TypeScript로 TodoList를 생성하고 삭제해보자 ]]></title>
            <link>https://velog.io/@1109_haeun/ReactTypeScript</link>
            <guid>https://velog.io/@1109_haeun/ReactTypeScript</guid>
            <pubDate>Fri, 31 May 2024 14:56:01 GMT</pubDate>
            <description><![CDATA[<h3 id="0531">0531</h3>
<blockquote>
<p>이 글에서는 React와 TypeScript로 TodoList의 추가, 삭제 기능을 구현하는 프로젝트 내용을 담고 있습니다. </p>
</blockquote>
<ul>
<li>깃허브 링크는 포스팅 하단에 첨부해두겠습니다.</li>
<li>깃허브의 코드는 변형되었을 수 있습니다. 커밋 로그를 확인해주세요. </li>
</ul>
<br>

<h1 id="🗂️-폴더-구조">🗂️ 폴더 구조</h1>
<pre><code>root
    ├── node_modules
    ├── public
    ├── src
    │   ├── components
    │   │   ├── NewTodo.module.css
    │   │   ├── NewTodo.tsx
    │   │   ├── TodoItem.module.css
    │   │   ├── TodoItem.tsx
    │   │   ├── Todos.module.css
    │   │   ├── Todos.tsx
    │   ├── models
    │   │   ├── todo.ts
    │   ├── App.css
    │   ├── App.tsx
    │   ├── index.css
    │   ├── index.tsx
    │   └── react-app-env.d.ts
    ├── .gitignore
    ├── package-lock.json
    ├── package.json
    ├── README.md
    └── tsconfig.json</code></pre><br>

<h2 id="⚙️-tsconfigjson">⚙️ tsconfig.json</h2>
<blockquote>
<p>ts -&gt; js로 컴파일하는 경우에 이 파일로 컴파일과 관련된 사항을 구성합니다. </p>
</blockquote>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;lib&quot;: [
      &quot;dom&quot;,
      &quot;dom.iterable&quot;,
      &quot;esnext&quot;
    ],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;
  },
  &quot;include&quot;: [
    &quot;src&quot;
  ]
}
</code></pre>
<ul>
<li><p>target: 작성한 ts코드를 어떤 버전의 js코드로 변환할건지 결정합니다. </p>
</li>
<li><p>lib: 기본 타입스크립트 라이브러리</p>
<ul>
<li>어떤 타입이 ts에서 기본으로 지원되는지를 결정합니다. </li>
<li>내장되어있기 때문에 별도 설치는 안 해도 되나 사용하려면 &#39;lib&#39;에 이름을 추가해야합니다.</li>
</ul>
</li>
<li><p>allowJS: js파일을 포함할 수 있는가의 여부를 결정합니다. </p>
</li>
<li><p><strong>strict: true로 설정하면 이 프로젝트에 가장 엄격한 설정이 적용됩니다.</strong></p>
<ul>
<li>ex) any타입 금지</li>
</ul>
</li>
<li><p>jsx: jsx코드를 지원할 것인가에 대한 여부를 결정합니다. </p>
</li>
</ul>
<br>

<h1 id="✅-todolist를-reacttypescript로-구현해보자">✅ TodoList를 React+TypeScript로 구현해보자</h1>
<h2 id="🧩-todots">🧩 todo.ts</h2>
<blockquote>
<p>Todo의 형태를 정의합니다. </p>
</blockquote>
<pre><code class="language-ts">// models/todo.ts
class Todo {
    id: string;
    text: string;

    constructor(todoText: string) {
        this.text = todoText;
        this.id = new Date().toISOString();
    }
}

export default Todo;</code></pre>
<p>어떠한 형태를 정의할 때는 interface, type, class 등을 사용할 수 있는데 여기서는 class의 형태로 Todo를 정의하였습니다. </p>
<p><code>Todo</code>의 <code>id</code>와 <code>text</code>에 값이 할당되는 부분이 없다면 에러가 발생합니다.
이는 &quot;인스턴스화가 되지 않기 때문&quot;입니다. </p>
<p>따라서 <code>constructor</code>로 값을 할당해줍니다.
이 때 <code>id</code>는 <code>Todo</code>가 만들어질 때마다 현재 날짜와 시간으로 자동 생성됩니다. </p>
<br>

<h2 id="🧩-apptsx">🧩 App.tsx</h2>
<blockquote>
<p>가장 메인이 되는 파일로 컴포넌트들이 렌더링됩니다. </p>
</blockquote>
<pre><code class="language-tsx">// App.tsx
import { useState } from &#39;react&#39;;

import NewTodo from &#39;./components/NewTodo&#39;;
import Todos from &#39;./components/Todos&#39;;
import Todo from &#39;./models/todo&#39;;

function App() {
  const [todos, setTodos] = useState&lt;Todo[]&gt;([]);/

  const addTodoHandler = (todoText: string) =&gt; {
    const newTodo = new Todo(todoText);
    setTodos((prevTodos) =&gt; {
      return prevTodos.concat(newTodo);
    });
  }

  const removeTodoHandler = (todoId: string) =&gt; {
    setTodos((prevTodos) =&gt; {
      return prevTodos.filter((todo) =&gt; todo.id !== todoId);
    })
  };

  return (
    &lt;div&gt;
      &lt;NewTodo onAddTodo={addTodoHandler} /&gt;
      &lt;Todos items={todos} onRemoveTodo={removeTodoHandler} /&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<br>

<h3 id="📍-usestate로-todo-상태-관리하기">📍 useState로 Todo[] 상태 관리하기</h3>
<pre><code class="language-tsx">const [todos, setTodos] = useState&lt;Todo[]&gt;([]);</code></pre>
<p>react가 제공하는 <code>useState</code>를 사용하여 <code>Todo</code> 배열의 상태를 관리합니다.</p>
<p>이 때 <code>useState([]);</code>로 빈 배열만 넘겨줄 경우에는 에러가 발생합니다.
빈 배열을 넘김으로써 <code>Todo</code>가 어떤 타입의 값을 가지는지가 모호해지기 때문입니다. </p>
<p>따라서 제네릭함수인 <code>useState</code>는 제네릭으로 <code>Todo[]</code> 라는 타입을 가진다고 알려주어야 합니다. </p>
<br>

<h3 id="📍-todo를-추가하는-함수">📍 Todo를 추가하는 함수</h3>
<pre><code class="language-tsx">  const addTodoHandler = (todoText: string) =&gt; {
    const newTodo = new Todo(todoText);
    setTodos((prevTodos) =&gt; {
      return prevTodos.concat(newTodo);
    });
  }</code></pre>
<p>파라미터로 받은 <code>todoText</code>를 가진 <code>newTodo</code>를 생성합니다.</p>
<p>그 후 <code>setState</code>로 이전의 todo list인 <code>prevTodos</code>에 <code>newTodo</code>를 더하여 할 일을 추가합니다.</p>
<br>

<h3 id="📍-todo를-삭제하는-함수">📍 Todo를 삭제하는 함수</h3>
<pre><code class="language-tsx">  const removeTodoHandler = (todoId: string) =&gt; {
    setTodos((prevTodos) =&gt; {
      return prevTodos.filter((todo) =&gt; todo.id !== todoId);
    })
  };</code></pre>
<p>동일하게 <code>setState</code>로 <code>Todo</code>를 삭제하는 코드입니다.</p>
<p>이전의 <code>Todo[]</code>들이 담긴 <code>prevTodos</code>에서 삭제할 todo의 id값인 <code>todoId</code>와 <code>Todo[]</code>에서의 <code>id</code>가 다른 것들로 새 배열을 만들어줍니다.</p>
<p>이것으로 <code>Todo</code>삭제를 구현할 수 있습니다. </p>
<br>

<h3 id="📍-newtodo-todos-컴포넌트-배치하기">📍 NewTodo, Todos 컴포넌트 배치하기</h3>
<pre><code class="language-tsx">  return (
    &lt;div&gt;
      &lt;NewTodo onAddTodo={addTodoHandler} /&gt;
      &lt;Todos items={todos} onRemoveTodo={removeTodoHandler} /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<ul>
<li><code>NewTodo</code> : 새 <code>Todo</code>를 생성하는 <code>form</code>입니다.</li>
<li><code>Todos</code> : 생성된 <code>Todo[]</code>를 보여주는 <code>list</code>입니다.</li>
</ul>
<p><code>NewTodo</code>에서 생성버튼을 누르면 <code>onAddTodo</code>가 실행되며 전달된 함수인 <code>addTodoHandler</code>를 통해 <code>Todo</code>가 생성됩니다.</p>
<p><code>Todos</code>에서 각각의 <code>Todo</code>는 <code>items</code>라는 이름으로 전달되며 <code>todos</code>를 클릭할 시 <code>onRemoveTodo</code>가 실행되며 전달된 함수인 <code>removeTodoHandler</code>를 통해 클릭된 <code>todos</code>가 삭제됩니다. </p>
<br>

<h2 id="🧩-todostsx">🧩 Todos.tsx</h2>
<blockquote>
<p>TodoItem 컴포넌트가 렌더링되어 TodoList를 볼 수 있습니다. </p>
</blockquote>
<pre><code class="language-tsx">// components/Todos.tsx
import React from &#39;react&#39;;
import Todo from &#39;../models/todo&#39;;
import TodoItem from &#39;./TodoItem&#39;;

import classes from &#39;./Todos.module.css&#39;;

const Todos: React.FC&lt;{ items: Todo[]; onRemoveTodo: (id: string) =&gt; void }&gt; = (props) =&gt; {

    return (
        &lt;ul className={classes.todos}&gt;
            {props.items.map((item) =&gt; (
                &lt;TodoItem
                    key={item.id}
                    text={item.text}
                    onRemoveTodo={props.onRemoveTodo.bind(null, item.id)}
                /&gt;
            ))}
        &lt;/ul&gt;
    );
}

export default Todos;</code></pre>
<br>


<h3 id="📍-reactfc란">📍 React.FC란?</h3>
<p>여기서 중요한 것은 <strong>React.FC</strong>입니다.</p>
<blockquote>
<p><strong>React.FC</strong> : 리액트 패키지에 정의된 타입으로 이를 통해 이 함수가 함수형 컴포넌트로 동작한다는 사실을 명확히 할 수 있습니다. </p>
</blockquote>
<p>또한 React.FC는 <strong>제네릭 타입</strong>입니다.
만약 React.FC를 타입으로 가지는 함수가 props와 같은 프로퍼티를 가진다면 해당 프로퍼티의 타입을 제네릭으로 정의할 수 있습니다. </p>
<pre><code class="language-tsx">const Todos: React.FC&lt;{ items: Todo[]; onRemoveTodo: (id: string) =&gt; void }&gt; = (props) =&gt; {</code></pre>
<ul>
<li>Todos는 props와 같은 파라미터를 받습니다.</li>
<li>이 때 파라미터는 타입이 <code>Todo[]</code>인 <code>items</code>와, 파라미터 <code>id</code>의 타입이 <code>string</code>이고 반환 타입이 <code>void</code>인 함수 <code>onRemoveTodo</code>가 있습니다. </li>
</ul>
<br>

<h3 id="📍-todoitem-컴포넌트-리스트로-렌더링하기">📍 TodoItem 컴포넌트 리스트로 렌더링하기</h3>
<blockquote>
<p>TodoItem 컴포넌트를 불러와 ul&gt;li list형식으로 TodoList 나타냅니다. </p>
</blockquote>
<pre><code class="language-tsx">    return (
        &lt;ul className={classes.todos}&gt;
            {props.items.map((item) =&gt; (
                &lt;TodoItem
                    key={item.id}
                    text={item.text}
                    onRemoveTodo={props.onRemoveTodo.bind(null, item.id)}
                /&gt;
            ))}
        &lt;/ul&gt;
    );</code></pre>
<ul>
<li><code>className</code> : css파일의 className을 의미합니다.</li>
</ul>
<p><code>Todos</code>의 <code>props</code>에는 <code>Todo[]</code>가 담긴 <code>items</code>가 존재합니다.
이 <code>items</code>를 <code>map</code>을 이용해 각각의 값 하나씩을 <code>item</code>이라는 이름으로 불러와 <code>Todo</code> class의 속성인 <code>id</code>와 <code>text</code>를 각각 불러옵니다.
불러온 <code>id</code>와 <code>text</code>는 <code>TodoItem</code> 컴포넌트에 전달하여 <code>Todo</code>를 리스트로 보여줄 수 있게 됩니다. </p>
<br>

<h3 id="📍-bind란">📍 bind()란?</h3>
<p><code>onRemoveTodo</code>에는 <code>TodoItem.tsx</code>에서 <code>TodoItem</code>이 클릭되었을 때 동작하는 <code>onRemoveTodo</code>함수를 건네주는데 이 때 <code>bind</code>함수가 사용됩니다.</p>
<blockquote>
<p>bind() : js에서 제공하는 메서드로 실행할 함수를 미리 설정할 수 있습니다. </p>
</blockquote>
<p>보통 삭제하기 위해서는 <code>id</code>같은 고유한 값이 전달되어야 하는데 그냥 <code>onRemoveTodo={props.onRemoveTodo}</code>로만 전달해버리면 <code>id</code>를 받을 수 없고 그로 인해 삭제할 값이 무엇인지를 판단할 수 없습니다.</p>
<p>따라서 <code>bind</code>를 통해 <code>item.id</code>를 전달하는 것입니다. </p>
<br>

<h2 id="🧩-newtodotsx">🧩 NewTodo.tsx</h2>
<blockquote>
<p>사용자에게 입력창을 제공하고 사용자가 입력한 Todo의 내용을 가져올 수 있습니다. </p>
</blockquote>
<pre><code class="language-tsx">// components/NewTodo.tsx
import { useRef } from &#39;react&#39;;// 레퍼런스 생성 가능

import classes from &#39;./NewTodo.module.css&#39;;

const NewTodo: React.FC&lt;{onAddTodo: (text: string) =&gt; void }&gt; = (props) =&gt; {
    const todoTextInput = useRef&lt;HTMLInputElement&gt;(null);

    const submitHandler = (event: React.FormEvent) =&gt; {/
        event.preventDefault();

        const enteredText = todoTextInput.current!.value;
        if ( enteredText.trim().length === 0 ) {
            return;
        }

        props.onAddTodo(enteredText);
    }

    return (
        &lt;form onSubmit={submitHandler} className={classes.form}&gt; 
            &lt;label htmlFor=&quot;text&quot;&gt;Todo text&lt;/label&gt;
            &lt;input type=&quot;text&quot; id=&quot;text&quot; ref={todoTextInput}/&gt;
            &lt;button&gt;Add Todo&lt;/button&gt;
        &lt;/form&gt;
    );
}

export default NewTodo;</code></pre>
<br>

<h3 id="📍-useref-사용하기">📍 useRef() 사용하기</h3>
<pre><code class="language-tsx">const todoTextInput = useRef&lt;HTMLInputElement&gt;(null);</code></pre>
<blockquote>
<p>&quot;react&quot; 에서 레퍼런스를 생성하기 위해 제공하는 <code>useRef</code>로 <code>html</code>의 <code>input</code> 요소를 연결한 코드입니다. </p>
</blockquote>
<ol>
<li>제네릭으로 타입을 표현해주세요.</li>
</ol>
<p><code>useRef</code>에 제네릭으로 타입이 표현되어 있는 이유는 <code>useRef</code>만으로 이 레퍼런스가 <code>input</code>에 연결될지 <code>button</code>에 연결될지를 모르기 때문입니다. </p>
<p><code>useRef</code>도 마찬가지로 제네릭 타입으로 정의되어 있습니다. </p>
<ol start="2">
<li>기본값을 설정해주세요. </li>
</ol>
<p>이 레퍼런스에 다른 요소가 할당되어 있을 수 있기 때문입니다.
현재로서는 <code>null</code>이 들어가 있으나 값을 입력한 뒤 제출 버튼을 눌렀을 때는 <code>todoTextInput</code>에 해당 <code>input</code>이 제대로 들어가있을 것입니다. </p>
<br>

<h3 id="📍-form-제출-함수">📍 Form 제출 함수</h3>
<blockquote>
<p>사용자가 form의 submit button을 클릭했을 때 호출됩니다. </p>
</blockquote>
<pre><code class="language-tsx">    // 폼 제출 함수
    const submitHandler = (event: React.FormEvent) =&gt; {
        event.preventDefault();

        const enteredText = todoTextInput.current!.value;/
        if ( enteredText.trim().length === 0 ) {
            return;
        }

        props.onAddTodo(enteredText);
    }</code></pre>
<br>

<pre><code class="language-tsx">    const submitHandler = (event: React.FormEvent) =&gt; {
        event.preventDefault();</code></pre>
<p><code>onSubmit</code> 이벤트를 수신할 때, 즉 <code>form</code>을 제출했을 때 자동적으로 받게 됩니다.</p>
<p><code>preventDefault()</code>로 자동 제출을 막을 수 있습니다. </p>
<br>

<h3 id="📍-current-vs-current">📍 current? vs current!</h3>
<pre><code class="language-tsx">        const enteredText = todoTextInput.current!.value;
        if ( enteredText.trim().length === 0 ) {
            return;
        }

        props.onAddTodo(enteredText);</code></pre>
<p><code>todoTextInput.current!.value</code>에는 <code>input</code>의 실제 텍스트 값이 들어가 있습니다.
이 때 기본적으로 <code>!</code> 가 아닌 <code>?</code> 가 처음으로 생기는데 이 둘의 차이를 알아봅시다. </p>
<blockquote>
<p><strong>?</strong> : 일단 값에 접근은 해보고 접근이 가능하면 입력된 값을 가져와서 enteredText에 저장해</p>
</blockquote>
<ul>
<li>이 때 <code>enteredText</code>의 타입에는 <code>undefined</code>가 추가됩니다. </li>
</ul>
<blockquote>
<p><strong>!</strong> : 이 값이 null이 될 수 있다는 것은 알지만 이 시점에서는 절대 null이 아니니까 입력된 값을 가져와서 enteredText에 저장해</p>
</blockquote>
<ul>
<li>null이 아님을 100% 확신할 때 사용해야 합니다. </li>
</ul>
<br>

<p><code>enteredText</code>에 공백을 제외한 입력값이 있을 경우에 <code>props</code>로 받은 <code>onAddTodo</code>함수에 입력된 텍스트를 전달합니다. </p>
<p>이렇게 새 <code>Todo</code>를 저장할 수 있습니다! </p>
<br>

<h2 id="🧩-todoitemtsx">🧩 TodoItem.tsx</h2>
<blockquote>
<p>각각의 할 일들을 리스트요소로 담아 보여줍니다. </p>
</blockquote>
<pre><code class="language-tsx">// components/TodoItem.tsx
import classes from &#39;./TodoItem.module.css&#39;

const TodoItem: React.FC&lt;{text: string, onRemoveTodo: () =&gt; void }&gt; = (props) =&gt; {
    return (
        &lt;li className={classes.item} onClick={props.onRemoveTodo}&gt;{props.text}&lt;/li&gt;
    );
}

export default TodoItem;</code></pre>
<p><code>props</code>에서 받아온 <code>text</code>를 보여주고 해당 <code>list</code>를 클릭했을 때 할 일을 삭제하는 <code>onRemoveTodo</code>를 실행하여 할 일을 삭제합니다. </p>
<br>

<h2 id="🎨-css-파일-적용하기">🎨 CSS 파일 적용하기</h2>
<blockquote>
<p>css를 추가하여 조금 더 다듬어진 form을 구현해봅시다. </p>
</blockquote>
<pre><code class="language-css">/* Todos.module.css */
.todos {
  list-style: none;
  margin: 2rem auto;
  padding: 0;
  width: 40rem;
}</code></pre>
<pre><code class="language-css">/* NewTodo.module.css */
.form {
    width: 40rem;
    margin: 2rem auto;

  }

  .form label {
    display: block;
    font-weight: bold;
    margin-bottom: 0.5rem;
  }

  .form input {
    display: block;
    width: 100%;
    font: inherit;
    font-size: 1.5rem;
    padding: 0.5rem;
    border-radius: 4px;
    background-color: #f7f5ef;
    border: none;
    border-bottom: 2px solid #494844;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
    margin-bottom: 0.5rem;
  }

  .form button {
    font: inherit;
    background-color: #ebb002;
    border: 1px solid #ebb002;
    color: #201d0f;
    padding: 0.5rem 1.5rem;
    border-radius: 4px;
    cursor: pointer;
  }

  .form button:hover,
  .form button:active {
    background-color: #ebc002;
    border-color: #ebc002;
  }</code></pre>
<pre><code class="language-css">/* TodoItem.module.css */
.item {
  margin: 1rem 0;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  padding: 1rem;
  background-color: #f7f5ef;
}</code></pre>
<ul>
<li><p>전
<img src="https://velog.velcdn.com/images/1109_haeun/post/27a9f392-f286-4e5a-9636-385798df3b09/image.png" alt=""></p>
</li>
<li><p>후
<img src="https://velog.velcdn.com/images/1109_haeun/post/3cb58f3a-043c-4e75-9377-92dcf9883998/image.png" alt=""></p>
</li>
</ul>
<p>이렇게 &quot;Todo text&quot; input에 할 일을 적고 &quot;Add Todo&quot; 버튼을 클릭하면 할 일이 아래에 추가되며, 각각의 할 일을 클릭하면 삭제되는 기능을 구현하였습니다!</p>
<br>

<h1 id="🚩-더-해보고-싶어요">🚩 더 해보고 싶어요</h1>
<p>몇 차례에 걸쳐 같은 프로퍼티를 계속 넘겨주는 것이 코드가 복잡해보였습니다.
그렇게 되니 자연스레 같은 타입을 여러 번 반복해서 작성하게 되니 쓸데없는 코드가 늘어나게 되었습니다.</p>
<p>나중에는 Context API를 사용하여 코드를 간결하게 만들어보고 싶습니다.</p>
<br>

<h2 id="🔗-github-코드">🔗 github 코드</h2>
<ul>
<li><a href="https://github.com/haeun-noh/study-react-typescript/tree/main/react-study-ts">🔗 github/haeun-noh/study-react-typescript/tree/main/react-study-ts</a></li>
</ul>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[MySQL] Error syncing database: ConnectionRefusedError [SequelizeConnectionRefusedError]: connect ECONNREFUSED ::1:3306]]></title>
            <link>https://velog.io/@1109_haeun/MySQL-Error-syncing-database-ConnectionRefusedError-SequelizeConnectionRefusedError-connect-ECONNREFUSED-13306</link>
            <guid>https://velog.io/@1109_haeun/MySQL-Error-syncing-database-ConnectionRefusedError-SequelizeConnectionRefusedError-connect-ECONNREFUSED-13306</guid>
            <pubDate>Mon, 27 May 2024 23:57:14 GMT</pubDate>
            <description><![CDATA[<h3 id="0528">0528</h3>
<br>


<h1 id="🚨-발생-배경">🚨 발생 배경</h1>
<p>MySQL을 사용하는 express서버를 실행하려고 하니 아래와 같은 에러가 발생하였습니다. </p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/a1e2a2b8-0567-4ef4-b4e1-011fcf24c644/image.png" alt=""></p>
<blockquote>
<p>Error syncing database: ConnectionRefusedError [SequelizeConnectionRefusedError]: connect ECONNREFUSED ::1:3306</p>
</blockquote>
<p>3306포트의 MySQL 서버에 연결하지 못했다는 것을 알 수 있었습니다.</p>
<br>

<h1 id="👀-원인-분석">👀 원인 분석</h1>
<h2 id="🎯-cmd에서-mysql-port와-pid-확인하기">🎯 cmd에서 MySQL port와 PID 확인하기</h2>
<p>cmd에서 MySQL에 접속하려 하니 아래와 같은 에러 메시지를 볼 수 있었고 이를 통해</p>
<blockquote>
<p>내 컴퓨터 환경에서 3306포트의 PID값은 &quot;10061&quot;이라는 것을 확인할 수 있었습니다. <img src="https://velog.velcdn.com/images/1109_haeun/post/8f080846-7cd0-465d-a7f7-99d01e2bc426/image.png" alt=""></p>
</blockquote>
<p>*<em>그렇다면 3306포트가 활성화되지 않은 게 아닐까?
*</em>
<br></p>
<h2 id="🎯-현재-활성화된-로컬-주소와-pid값-확인하기">🎯 현재 활성화된 로컬 주소와 PID값 확인하기</h2>
<p>아래의 명령어로 현재 활성화된 주소들을 출력할 수 있습니다. </p>
<pre><code>netstat -ano</code></pre><p><img src="https://velog.velcdn.com/images/1109_haeun/post/7622f23c-b67b-4e5f-8bf2-e1304333100a/image.png" alt="">결과를 확인해보니 제가 MySQL에서 쓰는 3306포트는 활성화가 돼있지 않은 상태였고 <strong>MariaDB에서 사용하는 3308포트만이 살아있는 것</strong>을 확인했습니다.</p>
<p>또한 3306 포트의 PID값도 존재하지 않았습니다. </p>
<br>

<h2 id="🎯-작업-관리자에서-실행중인-mysqldexe-확인하기">🎯 작업 관리자에서 실행중인 mysqld.exe 확인하기</h2>
<p>&quot;작업관리자&quot;에 들어가 스크롤을 내리다보면 mysqld.exe가 나오게 됩니다.</p>
<p>이 때 저는 MySQL, MariaDB 각각의 mysqld.exe가 나와야 하기 때문에 총 두 개가 떠야 하는데 이마저도 MariaDB만이 실행되고 있었습니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/4a4f8abd-a465-4438-abb6-8d6f867bd0d5/image.png" alt=""></p>
<br>

<h1 id="🛠️-해결-과정">🛠️ 해결 과정</h1>
<p>이제** MySQL이 실행되지 않았다는 문제점**을 발견하였으니 다시 실행 시키는 작업에 들어가겠습니다. </p>
<h2 id="🎯-서비스에서-mysql-시작시키기">🎯 서비스에서 MySQL 시작시키기</h2>
<p>&quot;서비스&quot;에 들어가 마찬가지로 스크롤을 내리다보면 MySQL을 발견할 수 있습니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/85399bb1-a3cf-4f7f-a3d1-78dde6450677/image.png" alt="">이 때 &quot;시작 유형&quot;이 &quot;사용 안 함&quot; 상태이니 이를 &quot;자동&quot;으로 바꾸어 시작할 준비를 해야합니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/d6527407-b122-4ca0-86ca-272e0ee5f7e3/image.png" alt="">1. &quot;시작 유형&quot;에서 &quot;자동&quot;을 선택합니다.
2. &quot;적용&quot;을 눌러 변경사항을 저장합니다.
3. &quot;확인&quot;을 눌러 설정 변경을 마칩니다. </p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/bb3437ed-b7d3-45f3-a204-2333ae09c054/image.png" alt="">
MySQL을 우클릭하여 &quot;시작&quot;을 눌러 MySQL을 재시작합니다. </p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/44e427d0-bc1a-4b31-b83f-d3a223105b11/image.png" alt="">이렇게 &quot;상태&quot;가 &quot;실행 중&quot;이 뜨면 정상적으로 실행이 된 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/8b59c244-5745-420e-ba2b-3cab11f12d70/image.png" alt="">제 express서버에서도 MySQL이 잘 연동이 된 모습을 확인할 수 있습니다. </p>
<br>

<h1 id="🎯-얻은-점">🎯 얻은 점</h1>
<blockquote>
<p>DB 서버에 연결하지 못했다면 port와 PID를 확인하고 작업 관리자에서 실행중인 프로세스를 확인한 다음 실행 중이지 않다면 서비스에서 재실행을 시켜주어야 합니다.</p>
</blockquote>
<br>

<p>이런 에러를 몇 번 마주하면서 &quot;작업관리자&quot;와 &quot;서비스&quot;는 반드시 확인해봐야 한다는 생각이 들었습니다.</p>
<p>현재 제 상태로는 대부분이 그 선에서 해결이 되었는데 현업에서 일을 할 때는 이번에 알게 된 &quot;PID&quot;라는 것이 중요해질 것 같다는 느낌적인 느낌을 받았습니다.</p>
<p>네트워크와 PID에 대해서 조금 더 공부하고 이를 해결 과정에 적용시켜봐야겠습니다.</p>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] Gmail SMTP로 Image, Video File을 첨부하여 메일을 송신해보자]]></title>
            <link>https://velog.io/@1109_haeun/SpringBoot-Gmail-SMTP%EB%A1%9C-%EC%97%AC%EB%9F%AC-Image-File%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@1109_haeun/SpringBoot-Gmail-SMTP%EB%A1%9C-%EC%97%AC%EB%9F%AC-Image-File%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 27 May 2024 11:15:04 GMT</pubDate>
            <description><![CDATA[<h3 id="0527">0527</h3>
<blockquote>
<p>이 글은 Gmail SMTP를 이용하여 다중 이미지 파일과 동영상 파일을 첨부하여 이메일을 보내는 Spring Boot 프로젝트 코드를 작성하는 내용을 다루며, <a href="https://velog.io/@1109_haeun/SpringBoot-Gmail-SMTP%EB%A1%9C-%EB%A9%94%EC%9D%BC%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%B4%EB%B3%B4%EC%9E%90">이전 포스팅 내용</a>에서 일부 이어집니다.</p>
</blockquote>
<br>

<h1 id="🗂️-폴더-구조">🗂️ 폴더 구조</h1>
<ul>
<li>IDE: IntelliJ 유료버전 (gradle)</li>
<li>Java version: 17<pre><code>main
  ├── java
  │   │   
  │   ├── controller
  │   │   ├── EmailSendController (class)
  │   ├── service
  │   │   ├── EmailService (interface)
  │   │   ├── impl
  │   │   │   ├── EmailServiceImpl (class)
  │   └── MailAppplication 
  │
  └── resources
      └── applicion.properties</code></pre></li>
</ul>
<br>

<h1 id="👀-emailsendcontroller">👀 EmailSendController</h1>
<pre><code class="language-java">package com.example.egudanna.controller;

import com.example.egudanna.service.email.EmailService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;


@RestController
@RequestMapping(&quot;/mail&quot;)
public class EmailSendController {
    private EmailService emailService;

    public EmailSendController(EmailService emailService) {
        this.emailService = emailService;
    }

    @PostMapping(&quot;/send&quot;)
    public String sendMail(@RequestParam(value = &quot;file&quot;, name = &quot;file&quot;) MultipartFile[] file,
                           @RequestParam(&quot;to&quot;) String to,
                           @RequestParam(&quot;cc&quot;) String[] cc,
                           @RequestParam(&quot;subject&quot;) String subject,
                           @RequestParam(&quot;body&quot;) String body) {
        return emailService.sendMail(file, to, cc, subject, body);
    }

}</code></pre>
<ul>
<li><code>@RestController</code> : @Controller + @ResponseBody가 합쳐진 것입니다. 따라서 값을 json의 형태로 반환합니다. </li>
<li><code>@RequestMapping</code> : 접속할 url을 나타냅니다. url중에서 공통된 부분은 class밖으로 빼내어 간결하게 코드를 작성할 수 있습니다. </li>
<li><code>@RequestParam</code> : 한 번에 하나의 파라미터의 값을 저장하기 위해 사용됩니다. </li>
</ul>
<blockquote>
<p>(주의) 스프링 부트 3.2부터 스프링부트가 매개변수의 이름을 인식하지 못하는 문제가 발생하였고 이를 해결하기 위해서는 파라미터의 이름을 명시해주어야 합니다. </p>
</blockquote>
<p>매개변수를 인식하지 못하는 어노테이션들 중에는 <code>@RequestParam</code>도 들어있기 때문에 <code>@RequestParam String to</code>가 아닌 <code>@RequestParam(&quot;to&quot;) String to</code>로 작성해주어야 정상적으로 파라미터의 값을 읽어들여 오류가 발생하지 않게 됩니다. </p>
<br>

<ul>
<li><p><code>file</code> : 이미지</p>
</li>
<li><p><code>to</code> : 메일을 받을 대상</p>
</li>
<li><p><code>cc</code> : 주 수신자(<code>to</code>) 외의 참조 수신자들</p>
<ul>
<li>이메일이 전송될 때 한 번에 여러 <code>cc</code>들에게 메일을 보내며 <code>cc</code>들은 메일의 복사본을 받습니다. </li>
</ul>
</li>
<li><p><code>subject</code> : 메일의 제목</p>
</li>
<li><p><code>body</code> : 메일의 본문</p>
</li>
</ul>
<br>

<h1 id="👀-emailservice">👀 EmailService</h1>
<p>위의 코드를 치면 <code>sendMail</code>줄에 빨간줄이 생길텐데 <code>Create method &#39;sendMail&#39; in &#39;EmailService&#39;</code>를 누르면 EmailService에  <code>sendMail</code>이 정의됩니다. </p>
<pre><code class="language-java">package com.example.egudanna.service.email;

import org.springframework.web.multipart.MultipartFile;

public interface EmailService  {
    String sendMail(MultipartFile[] file, String to,String[] cc, String subject, String body);
}</code></pre>
<p>EmailService는 interface이므로 &#39;구현&#39;이 아닌 &#39;정의&#39;가 됩니다. </p>
<br>

<h1 id="👀-emailserviceimpl">👀 EmailServiceImpl</h1>
<blockquote>
<p>interface인 EmailService를 상속받아 구현하는 클래스입니다.</p>
</blockquote>
<pre><code class="language-java">package com.example.egudanna.service.email.impl;

import com.example.egudanna.service.email.EmailService;
import jakarta.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class EmailServiceImpl implements EmailService {

    @Value(&quot;${spring.mail.username}&quot;)
    private String fromEmail;

    @Autowired
    private JavaMailSender javaMailSender;

    @Override
    public String sendMail(MultipartFile[] file, String to, String[] cc, String subject, String body) {
        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();

            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);

            mimeMessageHelper.setFrom(fromEmail);
            mimeMessageHelper.setTo(to);
            mimeMessageHelper.setCc(cc);
            mimeMessageHelper.setSubject(subject);
            mimeMessageHelper.setText(body);

            for (int i = 0; i &lt; file.length; i++) {
                mimeMessageHelper.addAttachment(
                        file[i].getOriginalFilename(),
                        new ByteArrayResource(file[i].getBytes()));
            }

            javaMailSender.send(mimeMessage);

            return &quot;mail send&quot;;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }
}</code></pre>
<br>

<h2 id="⚙️-설정파일의-값-가져오기">⚙️ 설정파일의 값 가져오기</h2>
<p>*<em>application.properties에서 smtp설정이 완료되었다는 전제하에 *</em> <code>@Value()</code>로 메일 송신자값을 가져오겠습니다. </p>
<p>송신자는 항상 동일하므로 텍스트로 주기보다는 설정파일에 적혀있는 값을 가져오는 것이 훨씬 유연합니다. </p>
<pre><code class="language-java">    @Value(&quot;${spring.mail.username}&quot;)
    private String fromEmail;</code></pre>
<br>

<h2 id="👩💻-sendmail-overriding하기">👩‍💻 sendMail Overriding하기</h2>
<blockquote>
<p>EmailService interface에 선언되어있는 sendMail()을 구현합니다.</p>
</blockquote>
<ul>
<li><p><code>MimeMessage</code> : 받아온 이메일로 메시지 폼을 만들어 메시지를 반환하는 함수로 이메일의 제목, 본문, 수신자, 발신자 및 첨부 파일과 같은 다양한 속성을 지정할 수 있습니다. </p>
</li>
<li><p><code>addAttachment</code> : 이미지 파일 이름과 바이트를 저장하여 이미지 파일을 저장할 수 있습니다. </p>
</li>
<li><p><code>send</code> : 이메일의 제목, 본문, 수신자, 발신자, 첨부파일이 담긴 메시지를 반환합니다.</p>
</li>
<li><p><code>return &quot;mail send&quot;;</code> : 메일이 정상적으로 보내졌다면 <code>mail send</code>라는 문자열을 반환합니다.</p>
</li>
</ul>
<br>

<h1 id="📤-postman으로-이미지-파일이-첨부된-메일-전송해보기">📤 Postman으로 이미지 파일이 첨부된 메일 전송해보기</h1>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/d8d53634-88af-432c-8b2d-0f6e68ebbe00/image.png" alt="">
Post로 New Request를 생성하고 &quot;Body &gt; form-data&quot;에서 각각의 파라미터 이름을 적어줍니다.</p>
<p>이 때 <code>file</code> 파라미터의 타입은 <code>File</code>로 지정해주어야 합니다.</p>
<p>순서대로 이미지파일 업로드, 수신자, 참조 수신자(메일의 복사본을 받을 사람), 메일 제목, 메일 본문의 값을 입력한 뒤 오른쪽 상단의 &quot;Send&quot; 버튼을 누르면 </p>
<p>사진의 아래처럼 &quot;mail send&quot;라는 메시지가 나오게 되고 메일 전송에 성공하게 됩니다. </p>
<blockquote>
<p>localhost:7000/mail/send url에서 port인 7000은 제가 application.properties파일에서 server.port=7000으로 따로 지정해준 것입니다.
위의 설정을 안 하셨다면 <strong>기본 포트인 8080</strong>으로 url를 작성해주세요. </p>
</blockquote>
<br>
메일을 확인하면

<p><img src="https://velog.velcdn.com/images/1109_haeun/post/60b49480-b7ec-4095-b889-bacb3c4d8a18/image.png" alt=""><code>to</code>에 적었던 메일에도</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/d62093c9-911e-4814-8108-b681ef62731b/image.png" alt=""><code>cc</code>에 적었던 메일에도 동일한 메일이 잘 전송된 것을 확인할 수 있습니다.</p>
<blockquote>
<p>혹시라도 메일이 오지 않았다면 &quot;스팸메일함&quot;에 메일이 보관되어있지 않은지 확인해주세요. </p>
</blockquote>
<br>

<h1 id="📤--영상-파일-전송해보기">📤 + 영상 파일 전송해보기</h1>
<h2 id="🚨-최대-업로드-사이즈를-초과했어요">🚨 최대 업로드 사이즈를 초과했어요</h2>
<p>영상 파일을 업로드하기 위해서는 최대 업로드 크기를 지정해주어야 합니다.</p>
<blockquote>
<p>만약 이제껏 작성한 상태 그대로 영상을 업로드할 경우에는 아래와 같은 에러가 발생하게 됩니다. </p>
</blockquote>
<blockquote>
<p>Resolved [org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded]
<strong>최대 파일 업로드 사이즈를 초과했다</strong>고 알려주고 있습니다. </p>
</blockquote>
<br>

<p>따라서 application.properties파일에 아래와 같이 맥시멈을 증가시켜줘야합니다.</p>
<pre><code class="language-java">spring.servlet.multipart.maxFileSize=10MB
spring.servlet.multipart.maxRequestSize=30MB</code></pre>
<p>저는 최대를 10MB로 지정하였는데 더 필요하다면 늘려주시면 됩니다. </p>
<br>

<h2 id="📤-postman에서-메일로-영상-송신하기">📤 Postman에서 메일로 영상 송신하기</h2>
<p>방식은 아까와 동일합니다.
단지 업로드하는 파일이 이미지에서 동영상으로 변경된 것 뿐입니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/f1b36800-e81c-479a-bbef-bb577390ac23/image.png" alt="">
저는 1분 남짓하는 영상을 첨부하였습니다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/1109_haeun/post/00948de0-7581-42c2-a0e3-0b715e9e6b32/image.png" alt="">메일에도 동영상 파일이 첨부된 것을 확인할 수 있습니다!</p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] Gmail SMTP로 간단한 텍스트 메일을 전송해보자]]></title>
            <link>https://velog.io/@1109_haeun/SpringBoot-Gmail-SMTP%EB%A1%9C-%EB%A9%94%EC%9D%BC%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@1109_haeun/SpringBoot-Gmail-SMTP%EB%A1%9C-%EB%A9%94%EC%9D%BC%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 26 May 2024 13:05:05 GMT</pubDate>
            <description><![CDATA[<h3 id="0526">0526</h3>
<blockquote>
<p>이 글은 Spring Boot를 사용하여 Gmail의 SMTP로 나에게 메일을 전송하는 코드를 다루고 있습니다. </p>
</blockquote>
<br>

<h1 id="🔐-gmail-앱-비밀번호-생성">🔐 Gmail 앱 비밀번호 생성</h1>
<blockquote>
<p>Spring Boot 프로젝트의 application.properties의 설정에 들어갈 앱 비밀번호를 생성합니다.</p>
</blockquote>
<p>앱 비밀번호란 차단된 앱이나 기기가 Google 계정에 액세스할 수 있도록 권한을 부여하는 것으로 <strong>일반 계정 비밀번호와는 다른 개념입니다.</strong></p>
<p>2단계 인증이 사용 설정된 계정에서만 생성되며 만약 앱 비밀번호를 생성한 Google 계정 비밀번호를 변경하면 앱 비밀번호가 취소되니 참고해주세요.</p>
<br>

<blockquote>
<p>다른 영상이나 글들에서는 단계별로 차근히 설명해주시지만 저같은 경우 앱 비밀번호가 &quot;Google 계정 관리 &gt; 보안&quot;탭에서 보이지 않아 애를 먹은 경헙이 있습니다.</p>
</blockquote>
<p>몇 번의 실패 후 </p>
<ul>
<li><a href="myaccount.google.com/apppasswords">myaccount.google.com/apppasswords</a></li>
</ul>
<p>위의 링크를 앱 비밀번호를 생성하고 싶은 계정으로 크롬 접속을 한 뒤 붙여넣어주면 바로 해당 계정의 &quot;앱 비밀번호&quot; 탭으로 넘어갈 수 있었습니다. </p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/b2f9e2b2-cdd4-49e8-baf2-7954144d91cb/image.png" alt=""></p>
<ol>
<li>App name: 앱 비밀번호를 나타내기 위한 이름</li>
<li>Create: 클릭 시 &#39;App name&#39;의 이름을 가진 앱 비밀번호가 생성됩니다.</li>
</ol>
<blockquote>
<p>앱 비밀번호를 생성하면 단 한 번만 보여주니 미리 메모장같은 곳에 붙여넣어주시길 바랍니다.</p>
</blockquote>
<ul>
<li>앱 비밀번호 생성 후 &quot;보안 수준이 낮은 앱의 엑세스&quot;를 &quot;허용&quot;으로 변경해주셔야합니다. </li>
</ul>
<br>

<h1 id="🗂️-spring-boot-프로젝트-생성">🗂️ Spring Boot 프로젝트 생성</h1>
<p>Spring Boot는 &#39;Spring Initializer&#39;라는 것으로 프로젝트를 생성합니다.</p>
<ul>
<li><a href="https://start.spring.io/">https://start.spring.io/</a></li>
</ul>
<p>위의 링크를 클릭하여 아래 사진과 동일하게 세팅 후 &#39;GENERATE&#39;를 눌러 프로젝트 zip 파일을 다운로드받아줍니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/dae7768c-4819-4590-b77f-003d874feee7/image.png" alt=""></p>
<ul>
<li>Artifact: 당신이 만들 프로젝트명을 입력해줍니다.</li>
<li>ADD DEPENDENCIES: 의존성을 추가합니다.<ul>
<li>Spring Boot DevTools: 코드를 업데이트했을 때 저장만으로 Server Rerun을 할 수 있습니다.</li>
<li>Spring Web</li>
<li>Lombok: 어노테이션을 사용할 수 있습니다.</li>
</ul>
</li>
</ul>
<br>

<h1 id="⚙️-applicationproperties-설정">⚙️ application.properties 설정</h1>
<ul>
<li>IDE: IntelliJ (Gradle)</li>
<li>version: java 17</li>
</ul>
<p>Gmail SMTP를 사용하기 위해서 <code>application.properties</code>에 어떠한 설정을 추가해야 합니다.</p>
<pre><code class="language-properties">spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=&lt;앱비밀번호를생성한계정@gmail.com&gt;
spring.mail.password=&lt;공백없는앱비밀번호&gt;
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true</code></pre>
<p>앱 비밀번호는 <code>aaaa aaaa aaaa aaaa</code> 이런 식으로 주어지게 되는데 <code>password</code>에서는 공백없이 <code>aaaaaaaaaaaaaaaa</code>로 작성합니다.</p>
<br>

<h1 id="📬-emailsenderservice-생성">📬 EmailSenderService 생성</h1>
<blockquote>
<p>Gmail을 전송하기 위한 sendMail()를 작성하겠습니다.</p>
</blockquote>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class EmailSenderService {
    @Autowired
    private JavaMailSender mailSender;

    public void sendMail(String toEmail,
                         String subject,
                         String body) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(&quot;자신의메일주소@gmail.com&quot;);
        message.setTo(toEmail);
        message.setText(body);
        message.setSubject(subject);

        mailSender.send(message);

        System.out.println(&quot;Mail Sent Successfully...&quot;);
    }
}</code></pre>
<h2 id="🧩-어노테이션-">🧩 어노테이션 (@)</h2>
<ul>
<li><code>@Service</code> : 비즈니스 로직을 처리하는 Service 클래스에 적용되며 해당 클래스를 스프링의 Bean에 등록합니다. </li>
<li><code>@Autowired</code> : DI(의존성 자동 주입)를 수행하는 데 사용되며 여기서는 스프링 컨테이너가 <code>JavaMailSender</code>의 Bean을 찾아서 자동으로 주입(생성)합니다.</li>
</ul>
<br>

<h2 id="👩💻-sendmail">👩‍💻 sendMail()</h2>
<ul>
<li><code>setFrom</code> : 메일을 보내는 주체</li>
<li><code>setTo</code> : 메일을 받는 대상</li>
<li><code>setText</code> : 메일의 본문 텍스트</li>
<li><code>setSubject</code> : 메일의 제목 부분</li>
<li><code>send</code> : 위의 네 가지 속성을 가진 메일 객체를 실제로 전송하는 메서드</li>
</ul>
<br>

<h1 id="👀-application에-eventlistener-생성">👀 Application에 EventListener 생성</h1>
<blockquote>
<p>SpringBoot 서버가 실행될 때 메일을 보내는 메서드인 sendMail가 실행되도록 EventListener를 설정해줄 것입니다.</p>
</blockquote>
<pre><code class="language-java">import com.example.egudanna.email.EmailSenderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;


@EnableJpaAuditing
@SpringBootApplication
public class EgudannaApplication {
    @Autowired
    private EmailSenderService senderService;

    public static void main(String[] args) {
        SpringApplication.run(EgudannaApplication.class, args);
    }

    @EventListener(ApplicationReadyEvent.class)
    public void sendMail() {
        senderService.sendMail(&quot;자신의메일주소@gmail.com&quot;,
                &quot;This is Subject&quot;,
                &quot;This is Body of Email&quot;);
    }
}</code></pre>
<ul>
<li><code>@EventListener</code> : 위 코드의 경우 이 어노테이션을 달면 Spring Boot 서버가 정상적으로 실행되었을 때(<code>ApplicationReadyEvent</code>) <code>sendMail()</code>를 실행합니다. </li>
<li><code>sendMail</code> : 만들어둔 메서드에 각각 &#39;메일을 전송하는 사람&#39;, &#39;메일의 제목&#39;, &#39;메일 안에 들어갈 text&#39;의 값을 전달합니다. </li>
</ul>
<br>

<h1 id="🎯-나에게-메일을-전송해보자">🎯 나에게 메일을 전송해보자!</h1>
<p>이렇게 작성한 Spring Boot 서버를 실행시키면 콘솔에 &quot;Mail Sent Successfully...&quot;가 출력됩니다.</p>
<p>이런 메시지가 출력되면 메일이 정상적으로 전송되었다는 뜻이며 자신의 gmail에 들어갔을 때 내가 나에게 보낸 메일을 볼 수 있게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/3f75bec1-7b52-4a23-a0af-1747c25f121d/image.png" alt=""></p>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] 타입스크립트의 정의와 사용해야하는 이유를 알아보고 환경을 구축해보자]]></title>
            <link>https://velog.io/@1109_haeun/Typescript-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%B4-%EA%B0%96%EC%B6%94%EC%96%B4%EC%95%BC-%ED%95%A0-%EA%B0%80%EC%9E%A5-%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8-%EC%A7%80%EC%8B%9D%EB%93%A4%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@1109_haeun/Typescript-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%B4-%EA%B0%96%EC%B6%94%EC%96%B4%EC%95%BC-%ED%95%A0-%EA%B0%80%EC%9E%A5-%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8-%EC%A7%80%EC%8B%9D%EB%93%A4%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 21 May 2024 14:15:59 GMT</pubDate>
            <description><![CDATA[<h3 id="0521">0521</h3>
<blockquote>
<p>타입스크립트의 정의와 왜 사용해야하는지, 그리고 타입스크립트를 사용하기 위한 기초 세팅을 다루는 글입니다. </p>
</blockquote>
<br>

<h1 id="1-타입스크립트란">1. 타입스크립트란?</h1>
<blockquote>
<p>자바스크립트의 &#39;슈퍼셋(superset)&#39; 언어</p>
</blockquote>
<p>타입스크립트는 js를 기반으로 하되, 보다 더 확장된 프로그래밍 언어입니다.</p>
<p>따라서 타입스크립트의 그 뿌리에는 여전히 자바스크립트가 있다는 것입니다.</p>
<p>자바스크립트의 문법을 대부분 사용하며 거기에 몇 가지 기능이 추가된 버전입니다.</p>
<br>

<h3 id="11-타입스크립트-왜-필요해">1.1. 타입스크립트, 왜 필요해?</h3>
<blockquote>
<p>타입스크립트는 자바스크립트의 주요 문법보다 확장된 문법을 갖습니다.</p>
</blockquote>
<p>자바스크립트는 <strong>동적 타입 언어</strong>이고 타입스크립트는 <strong>정적 타입 언어</strong>입니다.</p>
<br>

<ul>
<li><strong>동적타입언어</strong>: 런타임시 변수의 타입이 결정되는 언어입니다.</li>
</ul>
<p>코드를 실행할 때 알아서 변수 타입을 판단해 연산해주기 때문에 타입을 명시할 필요가 없습니다. </p>
<br>

<ul>
<li><strong>정적타입언어</strong>: 컴파일시 변수 타입이 결정되는 언어입니다.</li>
</ul>
<p>컴파일 시 자료형이 맞지 않으면 컴파일 에러가 나기 때문에 작성자가 직접 타입을 명시해주어야 합니다. </p>
<br>

<p>좀 더 자세히 알아봅시다.</p>
<pre><code class="language-js">function add(a,b) {
    return a + b;
}
const result = add(2,5);
console.log(result);</code></pre>
<p>자바스크립트 언어로 쓰여진 코드이며 실행할 경우 정수 타입의 <code>7</code>이 출력됩니다.
그렇다면 아래의 코드는 어떨까요?</p>
<br>

<pre><code class="language-js">function add(a,b) {
    return a + b;
}
const result = add(&#39;2&#39;,&#39;5&#39;);
console.log(result);</code></pre>
<p>문자열값이 들어가도 출력값은 문자열인 <code>25</code>로 나오게 되며 문제없이 실행됩니다.</p>
<blockquote>
<p>즉, 자바스크립트에서는 <code>a</code> <code>b</code>의 매개변수의 타입이 숫자라는 것을 누구도 알려주지 않습니다.</p>
</blockquote>
<p>이 때 타입 스크립트가 필요해집니다!</p>
<br>

<pre><code class="language-ts">function add(a: number,b: number) {
    return a + b;
}
const result = add(&#39;2&#39;,&#39;5&#39;);
console.log(result);</code></pre>
<p>위의 코드는 <code>a</code>와 <code>b</code>에 <code>number</code>라는 타입을 명시한 타입스크립트 코드입니다.</p>
<p><code>a</code>와 <code>b</code>의 타입이 숫자라는 것을 안 타입스크립트는 파라미터값을 문자열로 건네준 <code>const result = add(&#39;2&#39;,&#39;5&#39;);</code>에서 오류를 발생시키게 됩니다.</p>
<blockquote>
<p>이렇게 타입스크립트를 사용하면 코드를 작성할 때 바로 오류를 발견할 수 있습니다. </p>
</blockquote>
<br>

<h1 id="2-타입스크립트-사용환경-구축">2. 타입스크립트 사용환경 구축</h1>
<p>타입스크립트의 여러 자료형들에 대해 알아보기 전 타입스크립트 실습을 할 수 있는 환경을 구축해보겠습니다. </p>
<h2 id="21-타입스크립트-설치">2.1. 타입스크립트 설치</h2>
<p>node.js가 설치되어 있다는 가정 하에 진행하겠습니다.
만약 node.js가 설치되어 있지 않는다면 node.js를 먼저 설치하고 오시길 바랍니다.</p>
<br>

<p>타입스크립트를 사용할 프로젝트 안의 터미널에서 아래의 명령어를 작성하면 typescript가 설치됩니다.</p>
<pre><code class="language-bash">npm install typescript</code></pre>
<p>시스템 전체에서 사용할 수 있도록 설치하고 싶다면 아래의 명령어를 작성합니다.</p>
<pre><code class="language-bash">npm install -g typescript</code></pre>
<p>하지만 보통은 특정 프로젝트 안에 설치하는 것으로도 충분합니다.</p>
<br>

<pre><code class="language-bash">npm init -y</code></pre>
<p>빈 package.json파일을 생성하는 명령어입니다.
종속 라이브러리를 설치하는 데에 필요합니다.</p>
<br>

<h2 id="22-타입스크립트가-컴파일되는-과정">2.2. 타입스크립트가 컴파일되는 과정</h2>
<p>타입스크립트는 어떤 과정을 통해 컴파일이 될까요?</p>
<p>타입스크립트는 자바스크립트의 오프셋으로 자바스크립트의 문법에 타입 표기 구문이 추가된 것입니다.</p>
<blockquote>
<p>여기서 중요한 것은 타입스크립트의 코드가 브라우저에서 실행되지 않는다는 점입니다.
따라서 타입스크립트를 자바스크립트 형태로 컴파일해야합니다.</p>
</blockquote>
<p>타입스크립트가 컴파일이 되는 과정을 나열해봅시다.</p>
<ol>
<li>컴파일이 진행되는 동안 타입스크립트에 있는 타입 표기가 모두 삭제됩니다.<ul>
<li>자바스크립트는 타입 표기를 이해하지 못하기 때문입니다.</li>
</ul>
</li>
<li>타입스크립트에서 컴파일을 진행하며 코드작성창에 표시된 오류 알림 외의 추가적인 오류까지 알려줍니다.<ul>
<li>컴파일 단계에서는 코드를 작성하며 미처 발견하지 못했던 문제점들을 찾아 알려줍니다. </li>
</ul>
</li>
<li>이렇게 컴파일된 코드는 자바스크립트로 브라우저에서 실행이 될 것입니다.</li>
</ol>
<br>

<h2 id="23-명령어로-타입스크립트-컴파일-시작하기">2.3. 명령어로 타입스크립트 컴파일 시작하기</h2>
<p>만약 타입스크립트 구성 파일을 프로젝트 폴더에 추가하여 타입스크립트에게 컴파일할 파일을 알려주었다면 아래의 명령어를 입력하여 타입스크립트를 컴파일할 수 있습니다.</p>
<pre><code class="language-bash">npx tsc</code></pre>
<h3 id="231-오류가-발생해요">2.3.1. 오류가 발생해요!</h3>
<p>하지만 이 글에 명시된 명령어만 작성한 여러분들은 위의 명령어를 입력하면 에러가 날 것입니다.
구성파일이 없기 때문이죠.</p>
<p>구성파일이 없어도 컴파일할 파일을 지정해서 실행을 할 수 있습니다.</p>
<pre><code class="language-bash">npx tsc 파일명.ts</code></pre>
<p>만약 코드에 오류가 있다면 컴파일 오류가 뜰 것이고 문제가 없다면 아무런 로그도 뜨지 않을 것입니다.
이렇게 성공하면 <code>실행한파일명.js</code> 파일이 생성될텐데 기본적으로 오류가 발생했다하더라도 컴파일은 완료되며 자바스크립트 파일도 제공합니다.</p>
<h3 id="232-생성된-js-파일">2.3.2. 생성된 js 파일</h3>
<p>생성된 자바스크립트 파일은 실행한 타입스크립트 파일을 기본으로 합니다.</p>
<ul>
<li>즉, 타입이 제거된 버전으로 작성이 된다는 것입니다.</li>
<li>또한 <code>const</code>가 <code>var</code>로 변합니다.</li>
</ul>
<br>

<p>컴파일한 타입스크립트 파일에 오류가 없다면 생성된 자바스크립트 파일을 <code>index.html</code>같은 <code>html</code>파일에 옮겨 브라우저에서 사용해봅시다.</p>
<p>저장 후 페이지를 로드하면 자바스크립트 파일이 잘 실행되는 것을 볼 수 있습니다!</p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[Review] 고등학생 백엔드 지망생의 회사 인턴 합격 후기]]></title>
            <link>https://velog.io/@1109_haeun/Review-%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%A7%80%EB%A7%9D%EC%83%9D%EC%9D%98-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%B2%A4%EC%B2%98%EC%8A%A4-%EB%A9%B4%EC%A0%91-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@1109_haeun/Review-%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%A7%80%EB%A7%9D%EC%83%9D%EC%9D%98-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%B2%A4%EC%B2%98%EC%8A%A4-%EB%A9%B4%EC%A0%91-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 11 May 2024 15:13:23 GMT</pubDate>
            <description><![CDATA[<h3 id="0511">0511</h3>
<blockquote>
<p>저는 현재 마이스터고를 재학중에 있는 고등학교 3학년 학생입니다.
임상시험 회사인 JNPMEDI를 준비하면서 느낀 것들과 면접 후기를 나누기 위해서 작성하였습니다.</p>
</blockquote>
<br>

<h2 id="🔥불경기라-회사가-안-들어온다">🔥불경기라 회사가 안 들어온다..</h2>
<blockquote>
<p>제 학교에서는 회사에서 취업 오퍼가 들어오면 채용 프로세스를 진행하는 형태로 취업이 됩니다. </p>
</blockquote>
<p>선배들께 듣기만 했지 이렇게 심각할 줄 몰랐습니다.
코로나가 끝나고 지원금이 적어지고 경기가 안 좋아진 것이 영향을 준 것 같습니다.</p>
<p>작년 선배들이 취업이 된 회사 리스트를 보며 하나하나 찾아보았습니다.
그 중 제가 가고 싶은 회사 목록을 미리 짜두었지만 그 중 대부분이 올해 들어오지 않는다는 얘기를 선생님을 통해 듣게 되었습니다. </p>
<blockquote>
<p>그럼 전 어디로 가야하나요ㅠㅠ</p>
</blockquote>
<br>

<h2 id="✨채용설명회가-진행되다">✨채용설명회가 진행되다.</h2>
<blockquote>
<p>JNPMEDI의 채용설명회가 4월달에 교내에서 진행되었습니다.</p>
</blockquote>
<p>JNPMEDI는 제가 가고 싶어했던 회사 중 하나입니다.
하지만 본사가 인천 송도에 있었고 왕복 4시간이 걸렸기에 거리때문에 우선순위에서 밀렸습니다.</p>
<p>이 때까지만 해도 제가 JNPMEDI에 갈 줄 몰랐습니다..!</p>
<br>

<h2 id="❤️이-회사-꼭-가고-싶다">❤️이 회사 꼭 가고 싶다!</h2>
<p>설명회를 듣고 나서 확신이 들었습니다.</p>
<blockquote>
<p>이 회사 놓치면 앞으로 내가 원하는 회사가 들어오긴 힘들겠다.</p>
</blockquote>
<h4 id="🎁1-명확한-자회사의-서비스를-운영중에-있었다">🎁1. 명확한 자회사의 서비스를 운영중에 있었다.</h4>
<p>아버지와 주변의 개발자분들과 대화를 할 때면 공통적으로 하시는 말씀이 있었습니다. </p>
<blockquote>
<p>첫 회사는 자기만의 서비스를 운영하는 회사에 취업해라.
좋은 코드를 짜는 방법을 배울 수 있을 거야.</p>
</blockquote>
<p>그에 맞게 이 회사는 &quot;임상시험&quot;이라는 키워드를 가지고 여러 협업사와 전문가분들께 인정을 받고 있었고 저는 거기서 나오는 프라이드와 분명한 목적성에 끌렸습니다.</p>
<br>

<h4 id="🧸2-신입으로서-성장과-적응을-할-수-있는-시스템이-갖춰져있었다">🧸2. 신입으로서 성장과 적응을 할 수 있는 시스템이 갖춰져있었다.</h4>
<p>온보딩 시스템, 코드리뷰, 사내 스터디, 월별로 함께 모여 회사의 성장과 상태를 공유하는 문화 등등 여기라면 제가 성장할 수 있다고 생각했습니다.</p>
<br>

<h2 id="⏰채용-프로세스는">⏰채용 프로세스는??</h2>
<blockquote>
<ol>
<li>서류 (포트폴리오 &amp; 이력서)</li>
<li>코딩테스트 (GOORM)</li>
<li>1차 면접 (기술면접)</li>
<li>2차 면접 (임원면접)</li>
</ol>
</blockquote>
<p>힘들었던 점은 중간고사 기간과 절묘하게 겹쳤다는 점입니다.</p>
<p>서류를 받기 시작한 때가 중간고사 2주 전이었고
코딩테스트는 1주 전
면접은 모두 중간고사가 끝난 당일에 이루어졌습니다 하하</p>
<p>포트폴리오는 3일동안 꼬박 밤을 새서 준비했고
코딩테스트는 일주일동안 약 25개의 문제를 GROOM 플랫폼을 통해 풀며 준비했으며
면접은 자기소개와 지원동기를 외우고 나머지는 모두 CS 공부에 올인하며 준비했습니다. </p>
<p>코딩테스트는 집에서 4시간동안 총 4문제를 푸는 것이었는데 결과적으로는 1솔(..) 이었습니다. 
점수로는 100점 만점에 38점 정도가 나왔습니다.
평소 알고리즘과 자료구조, 그리고 코딩테스트를 꾸준히 풀지 않은지 반 년쯤 되었기에 많은 문제를 풀지 못했다는 생각이 들었습니다.</p>
<p>제한시간이 다 되어 제출하고 나서는 결과를 기대하면 안 되겠다는 생각을 했지만 다행스럽게도 면접 기회가 주어지게 되었습니다. </p>
<br>

<h2 id="📋중간고사가-끝나자마자-면접-보러-가야-한다구요">📋중간고사가 끝나자마자 면접 보러 가야 한다구요?</h2>
<blockquote>
<p>중간고사가 끝나는 바로 당일이 면접날로 잡히게 되었습니다.</p>
</blockquote>
<p>시험이 코앞에 다가오면 잠을 제대로 자지 않고 거의 밤을 새는 타입이기 때문에 정말 정신이 몽롱하고 잠이 쏟아졌습니다. </p>
<p>중간고사 준비하랴 면접 준비하랴 정신이 없었고 두 개를 해내야 한다는 압박감이 있었을뿐더러 잠을 제대로 못 잔 날이 계속되었기에 제정신으로 잘 대답할 수 있을까에 대한 불안감도 있었습니다. </p>
<p>여러 모로 복잡한 생각을 가지고 인천으로 출발했습니다. </p>
<p>면접은 인천 송도 포스코 타워 31층에서 진행됐습니다. </p>
<p>면접을 보기 위해서 31층으로 올라갔는데 올라가자마자 둘러보니까 어떤 분과 눈이 마주쳐서 얼떨결에 회사로 바로 들어가게 됐고 안내를 받아 면접 대기를 했습니다.</p>
<p>긴장하지 말라고 하시면서 면접비와 그에 대한 어떤 개인정보 수집을 하셨습니다.</p>
<p>조금의 대기시간동안 흐트러짐을 보이지 않으려고 자기소개도 숨어서 외웠던 게 기억이 납니다. </p>
<br>

<h2 id="🗨️생각보다-편안했던-면접-분위기">🗨️생각보다 편안했던 면접 분위기</h2>
<p>제가 생각했던 면접 분위기는 이거였습니다.</p>
<blockquote>
<p>&quot;우리가 딱 찝어줄테니 너가 언제 실수하나 보자.&quot;</p>
</blockquote>
<p>그런데 생각보다 긴장을 풀어주려고 노력을 엄청나게 해주셨고 덕분에 임원면접때에는 정말 동네 주민분들과 대화하듯 편하게 볼 수 있었던 것 같습니다.</p>
<p>중간중간 물 마시면서 해도 된다고 해주시고 최대한 긴장을 하지 않고 편안할 수 있도록 도와주시려는 게 눈에 보였습니다. </p>
<br>

<h3 id="💻기술-면접">💻기술 면접</h3>
<blockquote>
<p>기술 면접은 약 1시간정도 소요되었습니다. </p>
</blockquote>
<p>기술 면접때는 예상대로 꼬리질문이 정말 많이 나왔습니다.
한 질문에 평균 3~5개 정도의 꼬리질문이 나왔고 많게는 10개 넘게 꼬리 질문이 이어졌습니다. </p>
<p>열심히 준비한 만큼 최대한 대답을 하려고 했고 모르는 부분이 나왔다면 &quot;A는 잘 모르겠지만 B에 대해서는 알고 있습니다.&quot; 라며 최대한 공부한 지식을 써먹으려고 노력했던 것 같습니다.</p>
<p>특히 자료구조 질문에서 시간복잡도와 함께 설명을 드리니 &quot;오&quot; 하며 긍정적인 반응을 받을 수 있었습니다. </p>
<br>

<h3 id="🗣️회사-선배로서의-조언">🗣️회사 선배로서의 조언</h3>
<p>또한 조언도 많이 받을 수 있었습니다.</p>
<p>예를 들면</p>
<blockquote>
<p>&quot;국물 라면과 비빔면이 있을 때 어떤 것이 더 면치기하기 좋을까요?&quot; </p>
</blockquote>
<p>라는 질문에 </p>
<blockquote>
<p>&quot;저는 비빔면이 면치기하기에 더 좋을 것 같습니다. 왜냐하면 국물이 적어 튈 걱정이 줄기 때문입니다.&quot;</p>
</blockquote>
<p>라고 제가 답했다면 면접관님께서는 </p>
<blockquote>
<p>&quot;그런데 면치기라는 것에 집중을 한다면 국물 라면은 국물이 있기 때문에 더 매끄럽게 면치기가 되지 않을까요? 지원자분 대답이 틀린 것은 아닙니다만 그런 식으로도 생각할 수 있을 것 같습니다.&quot;</p>
</blockquote>
<p>와 같이 다양한 의견을 제시해주셨습니다.</p>
<p>또한 제가 작성한 코드를 보시며 이런 부분은 이렇게 고치면 더 RESTful한 코드가 될 것 같다고 조언도 해주셨습니다.</p>
<p>이렇게 면접을 하면서도 배울 점이 있었다는 부분에서 더 이 회사를 들어가 많은 것을 배우고 싶다고 생각했던 것 같습니다. </p>
<br>

<h3 id="🌞임원-면접">🌞임원 면접</h3>
<blockquote>
<p>임원 면접은 약 40분 정도 소요되었습니다. </p>
</blockquote>
<p>임원면접은 정말 편안한 분위기였습니다.
특히 가운데에 앉아계셨던 인사팀장분께서 편하게 분위기를 리드해주셨습니다. </p>
<p>최대한 제가 3년간 노력해왔던 점을 어필하려 노력하였고 면접을 보면 볼 수록 이 회사와 제가 핏이 맞을 것 같다는 느낌을 받았습니다.</p>
<p>긴장을 안 하는 것이 중요한 것 같습니다.
기술 면접에서는 긴장을 많이 해서 절은 부분이 있었는데 임원 면접에서는 긴장이 다 풀어져서 답변이 술술 나왔던 것 같습니다.</p>
<br>

<h2 id="🎉취뽀했습니다">🎉취뽀했습니다</h2>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/2b9d7a82-c52c-4f77-a834-26289afa69da/image.png" alt=""></p>
<blockquote>
<p>그냥 행복합니다😀</p>
</blockquote>
<p>감사하게도 운이 좋게 한 번만에 합격 소식을 들을 수 있었습니다.
취업을 준비하는 과정 속에서 이 회사에 들어간다면 굉장한 성장을 할 수 있을 것이라는 생각이 들었고 그것을 면접에서 잘 풀어 설명드렸던 것 같습니다.</p>
<p>1학기 안에 취업하는 것이 목표였는데 운이 좋게도 원하는 회사에 들어갈 수 있었다는 것에 정말 감사합니다. </p>
<br>

<h2 id="✅신뢰도-높은-신입이-되자">✅신뢰도 높은 신입이 되자</h2>
<p>취업 후 신입사원으로서의 태도가 무엇일지를 고찰하는 과정에서 굉장히 많은 자료들을 찾아보았습니다.</p>
<p>그를 통해서
<strong>회사에 엄청난 발전을 가져다 줄 인재</strong>
가 아니라 
<strong>작은 것부터 착실히 수행하여 신뢰감 있는 인재</strong>
가 되는것을 목표로 잡게 되었습니다. </p>
<p>앞으로는 회사에서 사용하는 typescript를 익히고
회사에 들어가면 여러 가지를 배우고 잘 질문하며 조직에 녹아드는 연습을 할 것입니다.</p>
<br>

<h2 id="🥹이-글을-마치며">🥹이 글을 마치며..</h2>
<p>사회생활이라고는 전혀 모르는 햇병아리가 덜컥 회사에 들어가게 되니 걱정이 태산입니다.</p>
<p>잘 적응 할 수 있을지..
회사 가서 울지는 않을지..
여러 용어들을 잘 알아들을 수는 있을지..
복교당하지는 않을지..
자취는 잘 맞을지..</p>
<p>이 모든 걱정을 하면서도 학교에서 3년동안 성장한 저보다 회사에서의 3개월간 성장한 제가 기대됩니다.</p>
<p>&quot;진짜&quot; 실무를 배울 수 있다는 점에서 굉장히 값진 기회를 얻은 만큼 후회없이 3개월을 보내보고 싶습니다.</p>
<p>몇몇의 사회초년생분들께서 이 글을 공감하셨으면 좋겠고 우리 모두 작은 것부터 잘 해내는 멋진 햇병아리가 되어봅시다!</p>
<p>긴 글 읽어주셔서 감사합니다😄</p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] 문자 개수 세기 (Java) ]]></title>
            <link>https://velog.io/@1109_haeun/Programmers-%EB%AC%B8%EC%9E%90-%EA%B0%9C%EC%88%98-%EC%84%B8%EA%B8%B0-Java</link>
            <guid>https://velog.io/@1109_haeun/Programmers-%EB%AC%B8%EC%9E%90-%EA%B0%9C%EC%88%98-%EC%84%B8%EA%B8%B0-Java</guid>
            <pubDate>Mon, 22 Apr 2024 04:59:14 GMT</pubDate>
            <description><![CDATA[<h3 id="0418">0418</h3>
<hr>
<h3 id="문제-설명">문제 설명</h3>
<p>알파벳 대소문자로만 이루어진 문자열 <code>my_string</code>이 주어질 때, <code>my_string</code>에서 <code>&#39;A&#39;</code>의 개수, <code>my_string</code>에서 <code>&#39;B&#39;</code>의 개수,..., <code>my_string</code>에서 <code>&#39;Z&#39;</code>의 개수, <code>my_string</code>에서 <code>&#39;a&#39;</code>의 개수, <code>my_string</code>에서 <code>&#39;b&#39;</code>의 개수,..., <code>my_string</code>에서 <code>&#39;z&#39;</code>의 개수를 순서대로 담은 길이 <code>52</code>의 정수 배열을 <code>return</code> 하는 <code>solution</code> 함수를 작성해 주세요.</p>
<h3 id="제한사항">제한사항</h3>
<p>1 ≤ my_string의 길이 ≤ 1,000</p>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>my_string</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td><code>&quot;Programmers&quot;</code></td>
<td><code>[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]</code></td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p><code>입출력 예 #1</code></p>
<p>예제 1번의 <code>my_string</code>에서 <code>&#39;P&#39;</code>가 1개, <code>&#39;a&#39;</code>가 1개, <code>&#39;e&#39;</code>가 1개, <code>&#39;g&#39;</code>가 1개, <code>&#39;m&#39;</code>이 2개, <code>&#39;o&#39;</code>가 1개, <code>&#39;r&#39;</code>가 3개, <code>&#39;s&#39;</code>가 1개 있으므로 <code>[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]</code>를 return합니다.</p>
<br>

<hr>
<h3 id="문제풀이">문제풀이</h3>
<p>저는 아래의 알고리즘으로 문제를 해결하였습니다. </p>
<ol>
<li><p>대소문자의 길이만큼 <code>answer</code> 배열을 선언합니다.</p>
</li>
<li><p><code>my_string</code>을 char 배열로 변환하여 <code>c</code>로 하나씩 가져옵니다.</p>
</li>
<li><p><code>c</code>가 대문자일 경우<code>&#39;A&#39;</code>만큼 뺀 인덱스의 <code>answer</code>값을 1 증가시킵니다.</p>
</li>
<li><p><code>c</code>가 소문자일 경우 <code>&#39;A&#39;+6</code>만큼 뺀 인덱스의 <code>answer</code>값을 1 증가시킵니다.</p>
<ul>
<li>대문자와 소문자 사이의 6개의 특수기호들 때문에 간극을 없애기 위해 6을 빼주었습니다. </li>
</ul>
</li>
</ol>
<p>이렇게 <code>1차</code> 코드를 완성했지만 숫자로 표현되어 있는 점과 코드의 중복에서 가독성이 좋지 않다는 것을 느꼈습니다.</p>
<br>

<p>이에 3, 4번을 변경하여 조금 더 간결화된 코드를 작성하였습니다.</p>
<ol start="3">
<li><p><code>c</code>에서 <code>A</code>를 빼줌으로써 인덱스가 <code>0</code>부터 시작하게 되었습니다. 하지만 소문자에서 <code>A</code>를 뺀다면 원래 있어야 할 인덱스보다 6칸이 뒤로 밀리게 됩니다. </p>
</li>
<li><p><code>(c &gt;= &#39;a&#39; ? 6 : 0)</code> - 따라서 <code>c</code>가 소문자이면 <code>6</code>을, <code>c</code>가 대문자이면 <code>0</code>을 빼주었습니다.</p>
</li>
</ol>
<p>이렇게 <code>2차</code> 코드까지 리팩토링을 완료하였습니다. </p>
<br>

<hr>
<h3 id="소스코드">소스코드</h3>
<p><code>1차</code></p>
<pre><code class="language-java">class Solution {
    public int[] solution(String my_string) {
        int[] answer = new int[52];
        for ( char c : my_string.toCharArray() ) {
            if ( c &lt; 97 )
                answer[c-65]++;
            else
                answer[c-71]++;
        }
        return answer;
    }
}</code></pre>
<p><code>2차</code></p>
<pre><code class="language-java">class Solution {
    public int[] solution(String my_string) {
        int[] answer = new int[52];
        for ( char c : my_string.toCharArray() ) {
            answer[(int)(c-&#39;A&#39;) - (c &gt;= &#39;a&#39; ? 6 : 0)]++;
        }
        return answer;
    }
}</code></pre>
<br>

<hr>
<h3 id="출력결과">출력결과</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/88179ceb-40e9-42f0-8fad-a1e8e565388f/image.png" alt=""></p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Constructor DI로 Bean을 등록해보자]]></title>
            <link>https://velog.io/@1109_haeun/Spring</link>
            <guid>https://velog.io/@1109_haeun/Spring</guid>
            <pubDate>Mon, 15 Apr 2024 12:20:50 GMT</pubDate>
            <description><![CDATA[<h3 id="0415">0415</h3>
<blockquote>
<p>spring에서의 constructor di에 대한 정의와 constructor di를 사용하여 Bean을 등록하는 예제를 다루는 글입니다.</p>
</blockquote>
<br>

<hr>
<h2 id="1-constructor-di란">1. <code>Constructor DI란?</code></h2>
<p><code>DI</code>란 <strong>의존성 주입</strong>을 뜻합니다.</p>
<p><code>DI</code>의 종류에는 총 두 가지가 있는데 </p>
<ul>
<li><code>Setter DI</code></li>
<li><code>Constructor DI</code></li>
</ul>
<blockquote>
<p>여기서 <strong>Constructor DI</strong>란 생성자를 통해 값을 주입하는 것을 말합니다.</p>
</blockquote>
<pre><code class="language-java">public Data1 data1() {
    return new Data1(30, &quot;data1&quot;);
}</code></pre>
<p>위의 코드처럼 생성자로 값을 초기화한다고 생각하면 됩니다.</p>
<br>

<hr>
<h2 id="2-constructor-di를-활용하여-값을-출력해보자">2. <code>Constructor DI를 활용하여 값을 출력해보자</code></h2>
<p><code>Constructor DI</code>를 익히기 위해 간단한 예제를 풀어보겠습니다.</p>
<h3 id="21-개발-환경">2.1. <code>개발 환경</code></h3>
<ul>
<li>*<em>IDE: *</em> IntelliJ, maven</li>
<li>*<em>Framework: *</em> Spring</li>
<li>*<em>폴더 구조: *</em><ul>
<li>src &gt; main &gt; java &gt; kr &gt; hs &gt; study &gt; beans: 객체들의 집합</li>
<li>src &gt; main &gt; java &gt; kr &gt; hs &gt; study &gt; config: java 설정파일</li>
</ul>
</li>
</ul>
<br>

<h3 id="22-문제-기본-세팅">2.2. <code>문제 기본 세팅</code></h3>
<h4 id="pomxml"><code>pom.xml</code></h4>
<p><code>Spring</code>을 사용하기 위해 꼭 필요한 파일입니다.
아래의 코드를 복사붙여넣고 다시 빌드를 하면 <code>Spring</code>을 사용할 수 있게 됩니다. </p>
<pre><code class="language-xml">&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;groupId&gt;kr.hs.study&lt;/groupId&gt;
    &lt;artifactId&gt;Component2&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;properties&gt;
        &lt;maven.compiler.source&gt;8&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;8&lt;/maven.compiler.target&gt;
    &lt;/properties&gt;
    &lt;dependencies&gt;
        &lt;!--   https://mvnrepository.com/artifact/org.springframework/spring-context   --&gt;
        &lt;!--   https://mvnrepository.com/artifact/org.springframework/spring-context   --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-context&lt;/artifactId&gt;
            &lt;version&gt;5.2.22.RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--   https://mvnrepository.com/artifact/org.slf4j/slf4j-api   --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
            &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
            &lt;version&gt;2.0.0&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--   https://mvnrepository.com/artifact/ch.qos.logback/logback-classic   --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
            &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
            &lt;version&gt;1.3.5&lt;/version&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
                    &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/project&gt;</code></pre>
<h4 id="chef"><code>Chef</code></h4>
<pre><code class="language-java">package kr.hs.study.beans;

public class Chef {
    private String name;
    private int age;

    public Chef(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}</code></pre>
<h4 id="order"><code>Order</code></h4>
<pre><code class="language-java">package kr.hs.study.beans;

public class Order {
    private String menu;
    private String drink;

    public Order(String menu, String drink) {
        this.menu = menu;
        this.drink = drink;
    }

    public String getMenu() {
        return menu;
    }

    public String getDrink() {
        return drink;
    }
}</code></pre>
<h4 id="restaurant"><code>Restaurant</code></h4>
<pre><code class="language-java">package kr.hs.study.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Restaurant {
    private Chef chef;
    private Order order;

    public Chef getChef() {
        return chef;
    }

    public Order getOrder() {
        return order;
    }
}</code></pre>
<h4 id="beanconfigclass"><code>BeanConfigClass</code></h4>
<p>실질적인 객체를 만들라는 명령을 spring에게 내리는 java 설정파일입니다. </p>
<pre><code class="language-java">package kr.hs.study.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = &quot;kr.hs.study.beans&quot;)
public class BeanConfigClass {

}</code></pre>
<h4 id="main"><code>Main</code></h4>
<pre><code class="language-java">package kr.hs.study;

import kr.hs.study.config.BeanConfigClass;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfigClass.class);
        ctx.close();
    }
}</code></pre>
<h4 id="출력되어야-하는-결과"><code>출력되어야 하는 결과</code></h4>
<pre><code>chef&#39;s name:ken
chef&#39;s age:30
Order menu:steak
Order drink:juice</code></pre><br>

<h3 id="23-constructor-di를-사용하여-올바른-결과를-출력해보자">2.3. <code>Constructor DI를 사용하여 올바른 결과를 출력해보자</code></h3>
<blockquote>
<p>문제 조건</p>
</blockquote>
<ol>
<li>Chef, Order 클래스는 수정하지 않습니다.</li>
<li>Setter DI를 사용하지 않습니다.</li>
<li>Main 클래스에서는 Restaurant의 객체만 불러옵니다.</li>
<li>BeanConfigClass 설정파일에서만 객체를 생성할 수 있습니다.</li>
</ol>
<br>

<ul>
<li><code>BeanConfigClass</code> <pre><code class="language-java">package kr.hs.study.config;
</code></pre>
</li>
</ul>
<p>import kr.hs.study.beans.Chef;
import kr.hs.study.beans.Order;
import kr.hs.study.beans.Restaurant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;</p>
<p>@Configuration
@ComponentScan(basePackages = &quot;kr.hs.study.beans&quot;)
public class BeanConfigClass {</p>
<pre><code>@Bean
public Chef c1() {
    return new Chef(&quot;ken&quot;, 30);
}

@Bean
public Order o1() {
    return new Order(&quot;steak&quot;, &quot;juice&quot;);
}

@Bean
public Restaurant r1() {
    return new Restaurant();
}</code></pre><p>}</p>
<pre><code>`BeanConfigClass`는 `Spring`에게 객체를 만들어라고 시키는 설정파일입니다.
이렇게 설정파일이라고 나타내는 것이 `@Configuration`입니다.

`@ComponentScan`은 `basePackages`안에 들어있는 `entity`들 중에서 `@Component`가 있는 것만 객체를 만들어라라고 지시하는 어노테이션입니다.

앞서 `BeanConfigClass`는 객체를 만드는 설정파일이라고 했었죠?
이 객체를 만드는 어노테이션이 바로 `@Bean`입니다.

`setter DI`를 사용하면 안 되고 `Constructor DI`를 사용해야 하기 때문에 `return`하는 객체의 생성자에 값을 집어넣어준 것을 확인할 수 있습니다.

또한 이렇게 만든 객체는 각각의 메서드명으로 `Main.java`에서 객체를 불러와 생성할 수 있습니다.
만약 `Chef`라는 객체를 가져온다면 `c1`이라는 이름으로 가져올 수 있겠군요!

&lt;br&gt;

- `Restaurant`
```java
package kr.hs.study.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Restaurant {
    @Autowired
    private Chef chef;
    @Autowired
    private Order order;

    public Chef getChef() {
        return chef;
    }

    public Order getOrder() {
        return order;
    }
}</code></pre><p><code>Chef</code>와 <code>Order</code>를 멤버변수로 가지고 있는 <code>Restaurant</code>입니다.
앞서 제시된 코드와 뭔가 다른 점이 생긴 것을 알아차리셨을 겁니다. </p>
<p>그건 바로 <code>@Autowired</code>인데요, 이 것은 쉽게 말해 <code>setter</code>의 역할을 해주는 어노테이션입니다.</p>
<p>우리는 설정파일에서 <code>Chef</code>와 <code>Order</code>는 생성자를 사용하여 초기화를 해주었지만 <code>Restaurant</code>의 생성자에는 아무것도 주지 않았습니다.</p>
<p>그렇다면 멤버변수인 <code>chef</code>와 <code>order</code>에는 아무것도 들어가지 않았기 때문에 <code>Restaurant</code>에서 <code>Chef</code>와 <code>Order</code>를 사용할 수 없게 되는데, 이 때 초기화를 자동으로 해주는 <code>@Autowired</code>덕분에 값이 자동으로 들어가, 사용이 가능해지게 되는 것입니다.</p>
<br>

<ul>
<li><code>Main</code><pre><code class="language-java">package kr.hs.study;
</code></pre>
</li>
</ul>
<p>import kr.hs.study.beans.Restaurant;
import kr.hs.study.config.BeanConfigClass;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;</p>
<p>public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfigClass.class);</p>
<pre><code>    Restaurant restaurant = ctx.getBean(&quot;r1&quot;, Restaurant.class);
    System.out.println(&quot;chef&#39;s name:&quot;+restaurant.getChef().getName());
    System.out.println(&quot;chef&#39;s age:&quot;+restaurant.getChef().getAge());
    System.out.println(&quot;Order menu:&quot;+restaurant.getOrder().getMenu());
    System.out.println(&quot;Order drink:&quot;+restaurant.getOrder().getDrink());

    ctx.close();
}</code></pre><p>}</p>
<p>```
<code>Main</code>에서는 <code>Restaurant</code>객체를 설정파일의 <code>r1</code> 메서드를 이용하여 생성한 뒤,</p>
<p><code>Restaurant</code>안의 <code>Chef</code>와 <code>Order</code>의 <code>getter</code>를 통해 아래와 같은 출력 결과를 만들어낼 수 있게 됩니다!</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/51d14da9-3adb-4468-82c9-29cfe8ece284/image.png" alt=""></p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] OSI 7계층에 대해서 알아보자]]></title>
            <link>https://velog.io/@1109_haeun/CS-OSI-7%EA%B3%84%EC%B8%B5%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@1109_haeun/CS-OSI-7%EA%B3%84%EC%B8%B5%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 14 Apr 2024 14:20:03 GMT</pubDate>
            <description><![CDATA[<h3 id="0414">0414</h3>
<blockquote>
<p>OSI 7계층의 정의와 송신부, 수신부, 각 계층별 역할에 대해 정리한 글입니다.</p>
</blockquote>
<br>

<hr>
<h2 id="1-osi-7계층이란">1. <code>OSI 7계층이란?</code></h2>
<blockquote>
<p><strong>OSI 7계층 (Open Systems Interconnection Reference Model 7 Layers)</strong>이란 ISO(국제 표준화 기구)에서 *<em>네트워크 통신이 이뤄지는 과정을 7단계로 나눈 네트워크 표준 모델입니다. *</em></p>
</blockquote>
<br>

<h3 id="11-osi-7계층은-왜-사용하는가">1.1. <code>OSI 7계층은 왜 사용하는가?</code></h3>
<ol>
<li><p><strong>복잡한 시스템을 보다 쉽게 설명할 수 있습니다.</strong></p>
<ul>
<li><p>엔지니어가 <code>OSI 모델</code>을 사용하여 네트워크 아키텍처 시스템을 구현하면 주요 기능에 따라 운영 계층을 분리할 수 있습니다.</p>
</li>
<li><p>이렇게 전체적인 기능을 분리하면 다른 사용자들의 시스템 이해를 도울 수 있습니다.</p>
</li>
</ul>
</li>
<li><p><strong>더 빠른 개발이 가능해집니다.</strong></p>
<ul>
<li>엔지니어 자신이 새 시스템을 구축할 때 어떠한 방식으로 통신하는가를 알고 있기 때문에 작업을 더 잘 이해할 수 있게 됩니다.</li>
</ul>
</li>
<li><p><strong>표준화가 쉬워집니다.</strong></p>
<ul>
<li>네트워크 통신 개발을 표준화하여 시스템에 대한 사전지식이 없는 사람이더라도 매우 복잡한 시스템을 신속하게 이해, 구축, 분해할 수 있도록 돕습니다. </li>
</ul>
</li>
</ol>
<br>

<h3 id="12-osi-7계층의-송수신-흐름">1.2. <code>OSI 7계층의 송수신 흐름</code></h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/ff1ec006-4086-4e91-b52d-63968a7e0f14/image.png" alt=""><code>OSI 7계층</code>의 송수신 흐름은 다음과 같이 나타낼 수 있습니다.</p>
<ul>
<li>데이터를 송신할 경우 <code>높은 계층 -&gt; 낮은 계층</code> 순서대로 데이터를 전달합니다.</li>
<li>데이터가 수신부(송신부)의 가장 낮은 계층인 <code>물리 계층</code>에 도달하면 데이터를 수신하게 되는데, <code>낮은 계층 -&gt; 높은 계층</code> 순서대로 데이터를 전달합니다.</li>
</ul>
<br>

<h3 id="13-데이터-캡슐화">1.3. <code>데이터 캡슐화</code></h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/fbe64562-6a51-4cdf-9133-a2e49e936449/image.png" alt=""><code>OSI 7계층</code>의 각 계층은 독립적이며 필요한 정보를 추가해 데이터를 가공하게 됩니다.</p>
<p>가공할 때에 <code>header</code>와 <code>trailer</code>가 붙게 되는데 이를 <strong>데이터 캡슐화</strong>라고 합니다. </p>
<p>데이터 수신부의 같은 계층에서 데이터 호환성을 높이고 오류를 최소화하기 위함입니다.</p>
<br>

<p>이렇게 데이터 송신부에서의 데이터 캡슐화가 완료되면 데이터는 수신부로 전달되어 <code>물리 계층</code>부터 <code>응용 계층</code>까지 <code>캡슐화</code>를 진행하며 붙었던 <code>header</code>와 <code>trailer</code>를 제거하는 <strong>역캡슐화</strong>를 진행하게 되는데요,</p>
<p>이 과정에서 각 계층은 필요한 제어 정보를 얻을 수 있습니다. </p>
<br>

<hr>
<h2 id="2-osi-7계층의-각-계층">2. <code>OSI 7계층의 각 계층</code></h2>
<h3 id="21-응용-계층-7계층">2.1. <code>응용 계층 (7계층)</code></h3>
<ul>
<li><code>HTTP, FTP</code> 등의 프로토콜을 응용 프로그램의 UI를 통해 제공합니다.</li>
</ul>
<h3 id="22-표현-계층-6계층">2.2. <code>표현 계층 (6계층)</code></h3>
<ul>
<li>데이터를 표준화된 형식으로 변경합니다.</li>
</ul>
<h3 id="23-세션-계층-5계층">2.3. <code>세션 계층 (5계층)</code></h3>
<ul>
<li>세션의 유지 및 해제 등 응용 프로그램 간 통신 제어와 동기화를 진행합니다.</li>
</ul>
<h3 id="24-전송-계층-4계층">2.4. <code>전송 계층 (4계층)</code></h3>
<ul>
<li>신뢰성 있는 데이터를 전달하기 위한 계층입니다.</li>
<li><code>TCP</code> <code>UDP</code>같은 전송 방식과 포트 번호 등을 결정합니다.</li>
</ul>
<h3 id="25-네트워크-게층-3계층">2.5. <code>네트워크 게층 (3계층)</code></h3>
<ul>
<li>데이터를 <code>송신부 -&gt; 수신부</code>까지 보내기 위한 최적의 경로를 선택하는 <strong>라우팅(routing)</strong> 작업을 수행합니다.</li>
<li>이 때 선택한 최적의 경로를 <code>라우트(route)</code>라고 합니다.</li>
<li>네트워크 계층의 장비로는 <code>라우터(router)</code>가 있습니다.</li>
</ul>
<h3 id="26-데이터-링크-계층-2계층">2.6. <code>데이터 링크 계층 (2계층)</code></h3>
<ul>
<li>데이터 흐름을 관리하며 데이터의 오류 검출 및 복구 등을 수행합니다.</li>
<li><code>bridge</code>, <code>switch</code>, <code>ethernet</code>이 장비에 해당합니다.</li>
</ul>
<h3 id="27-물리-계층-1계층">2.7. <code>물리 계층 (1계층)</code></h3>
<ul>
<li>데이터를 비트<code>bit</code> 단위의 <code>0</code>과 <code>1</code>로 변환한 후 장비를 사용하여 전송하거나 전기 신호를 데이터로 복원하는 작업을 수행합니다.</li>
<li><code>repeater</code>, <code>hub</code> 등이 장비에 해당합니다.</li>
</ul>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Go][Open API] ChatGPT openai를 활용하여 ai 답변을 받아보자 ]]></title>
            <link>https://velog.io/@1109_haeun/GoOpen-API-ChatGPT-openai%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-ai-%EB%8B%B5%EB%B3%80-%EB%B0%9B%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@1109_haeun/GoOpen-API-ChatGPT-openai%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-ai-%EB%8B%B5%EB%B3%80-%EB%B0%9B%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 08 Apr 2024 16:09:27 GMT</pubDate>
            <description><![CDATA[<h3 id="0409">0409</h3>
<blockquote>
<p>본 포스팅에서는 chatgpt api key 발급부터 계좌 등록, golang을 이용한 간단한 ai 답변 기능 구현을 다룹니다. </p>
</blockquote>
<hr>
<h2 id="1-open-ai-시작하기">1. Open AI 시작하기</h2>
<h3 id="11-open-ai-로그인회원가입">1.1. Open AI 로그인/회원가입</h3>
<ol>
<li><a href="https://openai.com/">Open AI</a>에 들어가 로그인/회원가입을 진행합니다.</li>
<li>로그인 후 아래의 화면에서 <code>API</code>를 클릭합니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/7f409be7-a0f6-4a71-8558-590731685d1b/image.png" alt=""></li>
</ol>
<h3 id="12-open-ai에서-계좌-등록하기">1.2. Open AI에서 계좌 등록하기</h3>
<p>무료로 사용이 가능하다고도 알고 있지만 저는 계좌를 등록하지 않았을 때 </p>
<pre><code>you exceeded your current quota, please check your plan and billing details. for more information on this error, read the do</code></pre><p>위와 같은 에러가 발생하였기 때문에 계좌를 등록해주겠습니다.</p>
<p>첫 번째로 계좌를 등록하고 난 뒤에는 5달러정도가 빠져나가는데 며칠이 지나면 돌아오게 되니 걱정하지 않으셔도 됩니다. 계좌 등록은 무료입니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/7ee3ff8c-55a5-42dc-aae3-31be84e0708d/image.png" alt=""><code>Settings &gt; Billing &gt; Add to credit balance</code>로 계좌를 등록해줍니다. 이 때 카드는 Visa 카드여야 합니다. </p>
<h3 id="13-api-keys-발급받기">1.3. API Keys 발급받기</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/70ca0459-5d24-413b-8dee-8d55e8bbc319/image.png" alt=""><code>API keys &gt; Create new secret key</code>를 클릭하여 새 <code>API Key</code>를 발급받습니다. 이 것 또한 무료이나 api 사용을 시작하게 되면 그에 따라 요금이 지불되게 됩니다.</p>
<p>생성을 완료하면 <code>key</code>가 나오게 되는데 단 한 번만 보여주니 따로 저장해두시길 바라겠습니다. </p>
<br>

<hr>
<h2 id="2-golang-프로젝트-세팅하기">2. GoLang 프로젝트 세팅하기</h2>
<h3 id="21-프로젝트-생성하기">2.1. 프로젝트 생성하기</h3>
<p><code>GoLand</code> 또는 <code>VSCode</code> <code>IDE</code>를 사용하여 프로젝트를 하나 만든 후 <code>main.go</code>파일을 만들어주세요.
모든 실습은 <code>main.go</code>파일에서 할 것입니다.</p>
<br>

<h3 id="22-go-openai-설치">2.2. go-openai 설치</h3>
<blockquote>
<p><strong>go-openai란?</strong> 개발자들이 golang으로 openai를 사용하기 위해 직접 만든 라이브러리</p>
</blockquote>
<p><code>Open AI</code> 공식문서를 보면 <code>Python</code>과 <code>Node.js</code>만 지원되는 것을 확인할 수 있습니다.</p>
<p>그래서 우리는 공식이 아니라 개발자들이 직접 만든 <code>go open ai</code> 라이브러리인 <code>go-openai</code>를 다운로드받아 사용할 것입니다.
<a href="https://github.com/sashabaranov/go-openai?tab=readme-ov-file">go-openai github</a></p>
<pre><code>go get github.com/sashabaranov/go-openai</code></pre><p>위의 명령어를 터미널에 입력하세요. </p>
<br>

<h3 id="23-godotenv-설치">2.3. godotenv 설치</h3>
<blockquote>
<p><strong>godotenv란?</strong> Go 언어로 작성된 패키지 중 하나로, 프로젝트의 루트 디렉토리에 있는 .env 파일을 로드하여 환경 변수를 설정하는 기능을 제공한다. </p>
</blockquote>
<p><code>.env</code> 파일은 주로 민감한 정보나 환경별로 변경될 수 있는 설정을 저장하는 데 사용됩니다. 파일은 아래와 같이 <code>key = valu</code>형태로 작성됩니다.</p>
<pre><code>API_KEY = &quot;your api key&quot;</code></pre><p>이 때 <code>github</code>에 올릴 경우 정보의 유출을 막기 위해 <code>.env</code>파일은 반드시 <code>.gitignore</code>에 추가해주어야 합니다!</p>
<br>

<pre><code>go get github.com/joho/godotenv</code></pre><p>위의 명령어를 터미널에 입력하세요. </p>
<p><code>.env</code>파일은 <code>root</code> 디렉토리 바로 아래에 위치시켜주었습니다.
여러분도 발급받은 <code>api key</code>를 <code>.env</code>파일에 변수로 추가해주세요. </p>
<br>

<hr>
<h2 id="3-golang으로-chat-gpt-api-사용해보기">3. GoLang으로 Chat GPT api 사용해보기</h2>
<p>이제는 발급받은 <code>key</code>를 사용하여 <code>chat gpt</code>를 나의 터미널로 가져와 사용해볼 것입니다. </p>
<h3 id="31-go-openai-라이브러리의-support-context-코드-적용하기">3.1. go-openai 라이브러리의 support context 코드 적용하기</h3>
<p>라이브러리는 <code>go-openai</code>를 사용하였고 참고 코드는 <a href="https://github.com/sashabaranov/go-openai?tab=readme-ov-file">go-openai github</a>의 <code>ChatGPT support context</code>이며 아래의 코드와 같습니다.</p>
<pre><code class="language-go">package main

import (
    &quot;bufio&quot;
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;strings&quot;

    &quot;github.com/sashabaranov/go-openai&quot;
)

func main() {
    client := openai.NewClient(&quot;your token&quot;)
    messages := make([]openai.ChatCompletionMessage, 0)
    reader := bufio.NewReader(os.Stdin)
    fmt.Println(&quot;Conversation&quot;)
    fmt.Println(&quot;---------------------&quot;)

    for {
        fmt.Print(&quot;-&gt; &quot;)
        text, _ := reader.ReadString(&#39;\n&#39;)
        // convert CRLF to LF
        text = strings.Replace(text, &quot;\n&quot;, &quot;&quot;, -1)
        messages = append(messages, openai.ChatCompletionMessage{
            Role:    openai.ChatMessageRoleUser,
            Content: text,
        })

        resp, err := client.CreateChatCompletion(
            context.Background(),
            openai.ChatCompletionRequest{
                Model:    openai.GPT3Dot5Turbo,
                Messages: messages,
            },
        )

        if err != nil {
            fmt.Printf(&quot;ChatCompletion error: %v\n&quot;, err)
            continue
        }

        content := resp.Choices[0].Message.Content
        messages = append(messages, openai.ChatCompletionMessage{
            Role:    openai.ChatMessageRoleAssistant,
            Content: content,
        })
        fmt.Println(content)
    }
}</code></pre>
<p>하지만 위의 코드는 <code>api key</code>가 코드에 보여지게 되고, 질문이 정해져 있어 활용도가 떨어집니다.</p>
<br>

<h3 id="32-go-openai-라이브러리-코드-변경해보기">3.2. go-openai 라이브러리 코드 변경해보기</h3>
<p>따라서 저는 <code>api key</code>는 <code>.env</code>파일에서 변수로 가져와 사용할 것이며, 사용자의 입력을 받아 <code>Content</code>값을 유동적으로 처리할 것입니다.</p>
<p>이에 아래와 같이 코드를 수정하였습니다. </p>
<pre><code class="language-go">package main

import (
    &quot;bufio&quot;
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;github.com/joho/godotenv&quot;
    openai &quot;github.com/sashabaranov/go-openai&quot;
    &quot;log&quot;
    &quot;os&quot;
)

func main() {
    // .env파일을 찾습니다. 
    if err := godotenv.Load(); err != nil {
        log.Fatalf(&quot;Error loading .env file: %v&quot;, err)
    }

    // API_KEY 환경 변수를 가져옵니다.
    apiKey := os.Getenv(&quot;API_KEY&quot;)

    // api key로 OpenAI 클라이언트를 생성합니다.
    client := openai.NewClient(apiKey)

    // 사용자에게 입력을 받습니다.
    reader := bufio.NewReader(os.Stdin)
    fmt.Print(&quot;사용자 입력: &quot;)
    userInput, _ := reader.ReadString(&#39;\n&#39;)
    // 항상 비슷한 형태의 대답을 보여주기 위해 텍스트 프롬프트를 사용자의 입력 뒤에 추가합니다. 
    baseStr := &quot;위에 대한 대답은 명확하며 반드시 발랄하게 ~요체를 쓰며, 친구같이 진심으로 위로해주며 취업, 학업, 인간관계, 건강, 금전, 개인 카테고리에 대한 고민을 듣고 친구가 친구에게 말하듯이 일상적인 말투로 진지하게 고민에 대한 조언을 스토리텔링으로 뻔하지 않게 답을 해줘&quot;

    // 사용자의 입력을 OpenAI API로 전달하여 응답을 받습니다.
    resp, err := client.CreateChatCompletion(
        context.Background(),
        openai.ChatCompletionRequest{
            // ai 모델로는 GPT3Dot5Turbo를 사용합니다. 
            Model: openai.GPT3Dot5Turbo,
            Messages: []openai.ChatCompletionMessage{
                {
                    // 사용자의 입력
                    Role:    openai.ChatMessageRoleUser,
                    // 사용자가 입력한 내용에 기본 텍스트 프롬프트를 더하여 값을 전달합니다. 
                    Content: userInput + baseStr,
                },
            },
        },
    )

    // 오류를 확인하고 출력합니다.
    // 만약 계좌가 연동되지 않았다면 이 곳에서 429 에러가 발생할 수 있습니다. 
    if err != nil {
        fmt.Printf(&quot;ChatCompletion error: %v\n&quot;, err)
        return
    }

    // 응답에서 첫 번째 선택의 내용을 출력합니다.
    fmt.Println(&quot;GPT-3.5 응답:&quot;, resp.Choices[0].Message.Content)
}</code></pre>
<br>

<h3 id="33-chat-gpt로-고민-해결해보기">3.3. Chat GPT로 고민 해결해보기</h3>
<p>이제 위의 코드를 실행하여 고민을 털어놓고 상담을 받아보겠습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/72b6471e-ffd7-432a-8eb2-7d3b9215f671/image.png" alt="">이렇게 따스한 조언을 구할 수 있게 되었네요!</p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FileZilla] FileZilla 서버 접속부터 파일 업로드, 프로젝트 배포까지! FileZilla의 모든 것을 알아보자 ]]></title>
            <link>https://velog.io/@1109_haeun/FileZilla-FileZilla-%EC%84%9C%EB%B2%84-%EC%A0%91%EC%86%8D%EB%B6%80%ED%84%B0-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80-FileZilla%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@1109_haeun/FileZilla-FileZilla-%EC%84%9C%EB%B2%84-%EC%A0%91%EC%86%8D%EB%B6%80%ED%84%B0-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80-FileZilla%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 04 Apr 2024 14:48:21 GMT</pubDate>
            <description><![CDATA[<h3 id="0404">0404</h3>
<hr>
<h2 id="1-filezilla란">1. <code>FileZilla</code>란?</h2>
<blockquote>
<p>빠르고 강력한 무료 FTP 클라이언트</p>
</blockquote>
<p>파일질라는 무료, 오픈 소스이며 여러 운영체제에서 사용 가능합니다.</p>
<p>대용량 파일 전송 등 다양한 기능을 제공하며, 무엇보다 서버가 있다면 프로젝트 배포가 정말 간단하여 사용하기 편리합니다. </p>
<p><code>FileZilla</code>를 사용하기 위해서는 먼저 다운로드를 해주어야 합니다.
혹시라도 <code>FileZilla</code>가 없으신 분들은 먼저 설치를 해주시기 바랍니다!</p>
<p><a href="https://filezilla-project.org/download.php">FileZilla - Client Download</a>
<a href="https://filezilla-project.org/download.php?type=server">FileZilla - Server Download</a></p>
<br>

<hr>
<h2 id="2-filezilla에-접속하기">2. <code>FileZilla</code>에 접속하기</h2>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/178b3838-2f48-4c78-8f7d-fc5a8bc355dc/image.png" alt="">
<code>filezilla</code>에서 서버에 접속하기 위해서는 &quot;파일(F)&quot; 아래의 버튼을 클릭하여 여러 설정을 해주어야 합니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/efe73f23-0db7-4348-9850-5626101002ca/image.png" alt=""></p>
<ol>
<li><p>&quot;내 사이트&quot;를 선택한 상태에서 &quot;새 사이트(N)&quot;버튼을 클릭한 뒤 사이트를 생성합니다.</p>
</li>
<li><p>&quot;일반&quot;에서 프로토콜을 <code>SFTP - SSH File Transfer Protocol</code>로 설정합니다.
저는 <code>ssh</code>를 사용하였기 때문에 <code>sftp</code>를 선택하였으나 자신의 서버 상황에 따라 맞게 선택해주시면 됩니다.</p>
</li>
<li><p>호스트와 포트 번호를 입력합니다.
저는 <code>ssh</code>를 사용하기 때문에 <code>ssh</code>의 기본 포트 번호인 <code>22</code>로 설정해주었습니다. </p>
<blockquote>
<p>http 포트번호 : 80<br>https 포트번호 : 443<br>ssh 포트번호 : 22    </p>
</blockquote>
</li>
<li><p>로그온유형을 선택합니다.</p>
<blockquote>
<p>내 컴퓨터 - 일반
남의 컴퓨터 - 인터렉티브 (접속할 때마다 입력)</p>
</blockquote>
</li>
<li><p>(선택) 배경색 변경도 가능합니다. 이는 파일질라를 여러 개 띄우고 작업할 때 쉽게 구분할 수 있도록 하기 위함입니다.</p>
</li>
<li><p>(선택) 해당 서버에 대한 비고를 입력할 수 있습니다. 이는 나중에 이 서버가 무슨 역할을 하는지를 쉽게 파악할 수 있게 됩니다.</p>
</li>
<li><p>마지막으로 &quot;연결&quot; 버튼을 눌러주세요. 만약 입력한 정보가 알맞다면 연결이 될 것이며, 여러 확인창이 뜰텐데 다 기본값으로 확인 눌러주시면 됩니다. <img src="https://velog.velcdn.com/images/1109_haeun/post/4844a4b6-bc0b-48a2-b6b8-1e631f2bb4d5/image.png" alt="">서버가 잘 연결되었네요!</p>
</li>
</ol>
<br>

<hr>
<h2 id="3-filezilla의-파일-구조와-accesslog-알아보기">3. <code>FileZilla</code>의 파일 구조와 <code>access.log</code> 알아보기</h2>
<p>사용자 기준 왼쪽 화면은 <code>내 컴퓨터</code>, 오른쪽 화면은 <code>서버</code>입니다. </p>
<br>

<h3 id="31-filezilla-파일구조">3.1. <code>FileZilla</code> 파일구조</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/4142be06-4bf2-45ef-9058-30c8c8a661da/image.png" alt=""></p>
<ul>
<li><code>/</code>: <code>root</code></li>
<li><code>home</code>: 나의 계정이 있는 폴더</li>
<li><code>host</code>폴더: 실제 내가 사용할 폴더</li>
</ul>
<p>저는 <code>host</code>가 <code>stu11</code>이기 때문에 <code>stu11</code>의 폴더를 사용할 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/418a29db-6974-4630-8f0a-bd205e7e68c8/image.png" alt=""></p>
<ul>
<li><p><code>logs</code>: 사용자별로 접속한 기록을 저장하는 파일이 들어있는 폴더</p>
<ul>
<li><code>access.log</code>: 사용자 접속 기록을 볼 수 있는 파일(ip)</li>
</ul>
</li>
<li><p><code>www</code>: 나의 프로젝트를 배포할 폴더</p>
</li>
</ul>
<p>위의 사진을 보면 <code>?</code>로 지정된 폴더가 많은데 이는 권한이 없어서 읽을 수 없다는 뜻입니다. </p>
<br>

<h3 id="32-accesslog-내-컴퓨터로-다운로드하기">3.2. <code>access.log</code> 내 컴퓨터로 다운로드하기</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/626097e1-850b-4f64-b7bb-f19958f9d02e/image.png" alt=""></p>
<p>왼쪽(내 컴퓨터)에서 바탕화면을 선택하고 <code>access.log</code> 우클릭 후 &quot;다운로드&quot;를 누르면 나의 바탕화면에 <code>access.log</code>파일이 다운됩니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/460e2aa8-941e-423f-ad1d-9a504c06a8d1/image.png" alt="">이후 내 컴퓨터의 바탕화면을 보면 <code>access.log</code>파일이 있는 것을 확인할 수 있습니다.</p>
<br>

<h3 id="33-accesslog에서-접속-기록-확인하기">3.3. <code>access.log</code>에서 접속 기록 확인하기</h3>
<p>메모장으로 <code>access.log</code>파일을 열면 지금까지 서버에 접속한 ip 기록을 다 확인할 수 있습니다. </p>
<p>이제 정말 접속을 하면 <code>access.log</code>에 기록이 남는지를 확인해보겠습니다. </p>
<ol>
<li><code>filezilla</code>에 접속한 서버 주소로 chrome에서 접속을 해줍니다.</li>
<li>서버에서 내 컴퓨터로 <code>access.log</code>파일을 다시 다운로드습니다.</li>
<li>내 컴퓨터로 다운로드받은 <code>access.log</code>를 메모장으로 열어줍니다.</li>
<li><code>access.log</code>의 맨아래에 방금 접속한 ip가 저장이 된 것을 확인합니다. </li>
</ol>
<br>

<h4 id="331-faviconico-404-에러">3.3.1. favicon.ico 404 에러(?)</h4>
<p>그런데 맨 마지막 줄을 보면</p>
<blockquote>
<p>&quot;GET /favicon.ico HTTP/1.1&quot; 404</p>
</blockquote>
<p>이런 에러 메시지가 있을 수도 있습니다.  </p>
<p>그런데 저는 <code>favicon.ico</code>라는 것을 요청한 적이 없습니다. 이것은 무엇일까요?
<code>favicon.ico</code>는 아래와 같습니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/de65c05b-3b79-4db5-ba6e-114b5a9abcc2/image.png" alt=""><code>chrome</code>에서 뜨는 로고 정도로 생각하시면 됩니다. </p>
<p>만약 로고 설정을 해주지 않았다면 이미지를 가져오는 것에 실패하여 <code>404(not found)</code>가 뜨지만 실질적으로 에러가 아니기 때문에 접속은 성공한 것입니다. </p>
<br>

<hr>
<h2 id="4-filezilla로-이미지-업로드하기">4. <code>FileZilla</code>로 이미지 업로드하기</h2>
<h3 id="41-서버에-이미지-업로드하기">4.1. 서버에 이미지 업로드하기</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/5d5ccb27-e92a-4623-91d3-20cf04a6a7ec/image.png" alt=""></p>
<ol>
<li><p>서버에 띄울 이미지를 바탕화면에 다운로드받습니다.</p>
<ul>
<li>이미지명은 모두 소문자로 바꿔주세요 (이미지 선택 + F2)</li>
</ul>
</li>
<li><p>마우스 우클릭 후 업로드를 클릭합니다.</p>
<ul>
<li>모든 선택창은 기본값으로 진행해주시면 됩니다. </li>
</ul>
</li>
</ol>
<p>오른쪽(서버)를 보면 이미지가 서버에 잘 올라간 것을 확인할 수 있습니다. </p>
<br>

<h3 id="42-서버에-업로드-된-이미지-확인하기">4.2. 서버에 업로드 된 이미지 확인하기</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/bcd16d4e-2195-4747-b62d-37df026650eb/image.png" alt="">
<code>서버주소/이미지.확장자</code>를 입력하면 내가 업로드한 이미지를 볼 수 있습니다.</p>
<p>저는 <code>chrome</code> 홈을 캡처한 사진을 올렸는데 잘 업로드된 것을 확인할 수 있었습니다!</p>
<br>

<hr>
<h2 id="5-indexphp-수정해보기">5. index.php 수정해보기</h2>
<p><code>chrome</code>에서 서버 주소로 접속하면 바로 뜨는 페이지가 바로 <code>index.php</code>입니다. </p>
<p>이번에는 확장자로 접속하는 것이 아니라 아예 초기 화면에 이미지를 업로드해보겠습니다. </p>
<h3 id="51-indexphp-다운로드받기">5.1. <code>index.php</code> 다운로드받기</h3>
<p><code>www</code>폴더 안의 <code>index.php</code>를 마우스 우클릭 후 바탕화면에 다운로드를 합니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/1c1c0aaa-bfb7-4570-bb48-868ac2fe5e09/image.png" alt=""></p>
<p>내 바탕화면에서 <code>index.php</code>를 메모장으로 열면 아래와 같은 코드가 뜰텐데요, <code>phpinfo()</code>에는 서버의 정보가 들어있기 때문에 꼭 주석 처리를 해주셔야 합니다. </p>
<pre><code class="language-php">&lt;?php
echo &quot;잘 동작 합니다!&quot;;
phpinfo();
?&gt;</code></pre>
<p>위의 코드를 아래와 같이 바꿔볼게요</p>
<pre><code class="language-php">&lt;html&gt;
&lt;head&gt;&lt;title&gt;내 첫 홈페이지&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
    &lt;img src=&quot;./myimage.png&quot; alt=&quot;안녕&quot;&gt;
&lt;/body&gt;
&lt;/html&gt;
&lt;?php
// echo &quot;잘 동작 합니다!&quot;;
// phpinfo();
?&gt;</code></pre>
<p>이미지를 서버에 띄우는 코드를 작성하였고 서버에 접속하면 아래와 같이 이미지가 잘 뜨게 됩니다!<img src="https://velog.velcdn.com/images/1109_haeun/post/2d94d772-f72b-4f8a-a0b2-01b3560dbcae/image.png" alt=""></p>
<br>

<hr>
<h2 id="6-실제-프로젝트-폴더-배포해보기">6. 실제 프로젝트 폴더 배포해보기</h2>
<p>이제는 실제로 개발한 프로젝트 폴더를 서버에 올려보도록 하겠습니다.</p>
<p>저는 client 프로젝트(html/css/js)만 업로드를 해주도록 할게요.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/548cda60-6069-4716-90e0-645dda020dfa/image.png" alt="">저는 &quot;Workey-Client&quot;라는 폴더를 서버에 업로드할 것입니다. </p>
<p>각자의 프로젝트 폴더를 업로드하시면 되고, 지금까지의 방법대로 진행해주시면 됩니다.</p>
<ol>
<li>프로젝트 폴더 우클릭 후 &quot;업로드&quot; 버튼 눌러 서버에 올린다.</li>
<li>서버에 접속한다. <ul>
<li>제 경우 이 때 접속 링크는 &quot;서버주소/workey/Workey-Client/[index.html]&quot;이 됩니다. </li>
<li><code>index.html</code>은 안 써주셔도 자동으로 들어가집니다. </li>
</ul>
</li>
</ol>
<p>도메인/workey/Workey-Client/index.html로 접속을 해준다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/36581b2f-9240-4526-a177-a337f39b8ba8/image.png" alt="">이렇게 프로젝트 배포가 완료된 것을 볼 수 있습니다!</p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Error][Python] 'pip'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다]]></title>
            <link>https://velog.io/@1109_haeun/PythonError-pip%EC%9D%80%EB%8A%94-%EB%82%B4%EB%B6%80-%EB%98%90%EB%8A%94-%EC%99%B8%EB%B6%80-%EB%AA%85%EB%A0%B9-%EC%8B%A4%ED%96%89%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EB%98%90%EB%8A%94-%EB%B0%B0%EC%B9%98-%ED%8C%8C%EC%9D%BC%EC%9D%B4-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@1109_haeun/PythonError-pip%EC%9D%80%EB%8A%94-%EB%82%B4%EB%B6%80-%EB%98%90%EB%8A%94-%EC%99%B8%EB%B6%80-%EB%AA%85%EB%A0%B9-%EC%8B%A4%ED%96%89%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EB%98%90%EB%8A%94-%EB%B0%B0%EC%B9%98-%ED%8C%8C%EC%9D%BC%EC%9D%B4-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Thu, 04 Apr 2024 09:48:35 GMT</pubDate>
            <description><![CDATA[<h3 id="0404">0404</h3>
<hr>
<h2 id="1-배경">1. 배경</h2>
<p><code>openai</code>를 설치하기 위해 <code>python</code>에서 <code>pip install openai</code> 명령어를 실행했더니</p>
<blockquote>
<p>&#39;pip&#39;은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다</p>
</blockquote>
<p>위와 같은 메시지가 뜨며 설치가 되지 않았습니다.</p>
<br>

<h2 id="2-원인">2. 원인</h2>
<p>바로 위의 에러메시지로 구글링을 해보니 환경변수 설정이 되지 않아 발생한 일이었습니다!</p>
<p><code>python</code>을 깔았을 때 <code>Scripts</code>라는 폴더 안에 <code>pip</code>가 들어있는데 이 <code>pip</code>의 주소를 환경변수에 설치해주지 않아 발생한 것입니다.</p>
<blockquote>
<p>Python이 설치되는 기본 경로: C:\Users\USER\AppData\Local\Programs\Python\Python311</p>
</blockquote>
<br>

<h2 id="3-해결">3. 해결</h2>
<p><code>Window</code> 기반 해결 방법입니다.</p>
<h3 id="31-환경-변수-접속하기">3.1. 환경 변수 접속하기</h3>
<ol>
<li>&quot;고급 시스템 설정&quot; 에 들어갑니다.</li>
<li>&quot;환경 변수(N)&quot; 을 클릭합니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/0b425eb0-b05e-41d4-ba1d-7e8a04842da2/image.png" alt=""></li>
</ol>
<h3 id="32-pip-환경-변수-설정하기">3.2. <code>pip</code> 환경 변수 설정하기</h3>
<ol>
<li><p>사용자 변수에 있는 &quot;Path&quot;를 더블클릭하여 들어갑니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/337230dd-338d-4c13-9c21-cb6e70cc1cf2/image.png" alt=""></p>
</li>
<li><p>두 개의 환경 변수 세팅을 해줍니다.</p>
<blockquote>
<ol>
<li>%USERPROFILE%\AppData\Local\Microsoft\WindowsApps</li>
<li>%USERPROFILE%\AppData\Local\Programs\Python\Python311\Scripts</li>
</ol>
</blockquote>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/8b9732fb-752b-48bb-9bee-bcef631a17af/image.png" alt=""></p>
<ol start="3">
<li><p>세팅 완료 후 확인을 눌러 저장합니다.</p>
</li>
<li><p><strong>새 cmd 또는 터미널</strong>에서 <code>pip -V</code>로 환경 변수 설정이 잘 되었는지를 확인합니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/15eb0de4-a32c-45e8-8344-1a5f89bfd9aa/image.png" alt=""></p>
</li>
</ol>
<p><code>openai</code>도 잘 설치가 되는 모습을 확인할 수 있었습니다!
<img src="blob:https://velog.io/a921b632-b7f6-4c20-83de-667576029184" alt="업로드중.."></p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MariaDB] MariaDB 설치 및 SpringBoot를 연동해보자 (DBeaver)]]></title>
            <link>https://velog.io/@1109_haeun/MariaDB-MariaDB-%EC%84%A4%EC%B9%98%EC%99%80-SpringBoot-%EC%97%B0%EB%8F%99%ED%95%B4%EB%B3%B4%EA%B8%B0-DBeaver%EB%A1%9C-%ED%99%95%EC%9D%B8%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@1109_haeun/MariaDB-MariaDB-%EC%84%A4%EC%B9%98%EC%99%80-SpringBoot-%EC%97%B0%EB%8F%99%ED%95%B4%EB%B3%B4%EA%B8%B0-DBeaver%EB%A1%9C-%ED%99%95%EC%9D%B8%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 25 Mar 2024 16:03:22 GMT</pubDate>
            <description><![CDATA[<h3 id="0326">0326</h3>
<p>동아리에서 <code>MariaDB</code>와 <code>SpringBoot</code>를 사용하는 프로젝트를 진행하게 되었습니다.</p>
<p>지금까지는 <code>MySQL</code>만 사용해왔기에 이번 기회에 <code>MariaDB</code>의 설치와 설정, 그리고 <code>SpringBoot</code>와의 연동까지 한 번 진행해보려 합니다.</p>
<br>

<hr>
<h2 id="1-mariadb-install">1. MariaDB Install</h2>
<p><a href="https://mariadb.org/download/?m=blendbyte&amp;t=mariadb&amp;p=mariadb&amp;r=10.6.17&amp;os=windows&amp;cpu=x86_64&amp;pkg=msi&amp;mirror=blendbyte">MariaDB 설치 링크</a>
<img src="https://velog.velcdn.com/images/1109_haeun/post/9457a229-8cfa-4bd3-bd7f-7996f5b1a568/image.png" alt=""><code>MariaDB Server 10</code>버전을 넘어가면 1년동안만 지속되기 때문에 위의 사진의 버전대로 맞춰주시면 됩니다. <code>Download</code>를 누르면 <img src="https://velog.velcdn.com/images/1109_haeun/post/317889e5-956a-463c-a1ad-cdac7b74db50/image.png" alt="">위와 같은 화면이 뜨며 <code>MariaDB</code> 설치 파일이 다운로드됩니다.<img src="https://velog.velcdn.com/images/1109_haeun/post/5077f026-46f0-4790-92c1-29df1dee5712/image.png" alt=""><code>Next</code>를 누르고
<img src="https://velog.velcdn.com/images/1109_haeun/post/614f9eba-84fc-4c7b-af52-454ffdf4cc85/image.png" alt="">라이센스 동의를 해줍니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/0fbb49e2-1c62-4149-9bc1-a2a43f916d84/image.png" alt=""><code>MariaDB</code> 설치 경로는 기본값으로 가겠습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/7400f509-7e83-47d3-86ac-acb2555b3624/image.png" alt="">여기서는 <code>MariaDB</code>의 <code>root</code>계정의 비밀번호를 입력한 뒤 <code>Use UTF8 as default server&#39;s character set</code>를 체크해주세요.
<img src="https://velog.velcdn.com/images/1109_haeun/post/cdb98ca4-ce0e-48b7-8c0c-3304e6a6e288/image.png" alt="">이 화면도 기본값 그대로 넘어가주시는데
<img src="https://velog.velcdn.com/images/1109_haeun/post/d07e130b-7234-4658-96ed-f08520ac0b41/image.png" alt="">만약 위와 같은 메시지가 나온다면 현재 사용하고 있는 다른 <code>db</code>의 <code>port</code>와 겹쳐 충돌이 난 것이니 <code>port</code>번호를 다르게 바꿔주면 됩니다.</p>
<p>저는 이미 <code>mysql</code>에서 <code>3306 port</code>를 사용중이었기 때문에 <code>3308</code>로 바꾸어주었습니다.<img src="https://velog.velcdn.com/images/1109_haeun/post/ac9d0b75-365c-41ab-9348-fda665a699cf/image.png" alt=""><code>Next</code>를 누른 뒤 <code>install</code>을 누르면<img src="https://velog.velcdn.com/images/1109_haeun/post/e50c6905-703b-4f38-86b5-277c3e598471/image.png" alt=""><code>MariaDB</code> 설치가 완료되었습니다!</p>
<br>

<hr>
<h2 id="2-dbeaver에-mariadb-database-연결하기">2. DBeaver에 MariaDB database 연결하기</h2>
<p><code>MariaDB</code>를 설치하면 자동으로 <code>HeidiSLQ</code>라는 <code>DBMS</code> 관리 툴이 설치됩니다.
이것을 그대로 사용하셔도 되지만 저는 <code>DBeaver</code>라는 또 다른 관리 툴이 이미 있었기에 그대로 사용하도록 하겠습니다.</p>
<p>만약 <code>DBeaver</code>가 없으신 분들께서는 설치를 하고 다음 단계를 진행해주시기 바랍니다.</p>
<br>

<h3 id="21-mysql에서-사용할-database-생성">2.1. mysql에서 사용할 database 생성</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/f9d814b6-256f-4a08-a290-a2bb097747f8/image.png" alt=""><code>DBeaver</code>에서는 내가 사용할 <code>db</code>를 연결해서 사용하게 해주기 때문에 미리 사용할 <code>db</code>를 생성해야 합니다.</p>
<p><code>MariaDB</code>를 설치할 때 내가 주었던 사용자, <code>port</code>, 비밀번호 등에 맞춰 계정에 접속해주세요.</p>
<p>저는 <code>root</code>계정에 <code>3308</code> <code>port</code>로 접속하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/cf6a67b5-85cf-4997-bbdf-871222f35b48/image.png" alt=""><code>show databases;</code>로 현재 사용할 <code>db</code>가 없는 것을 파악한 뒤 <code>create database mydbname</code>으로 <code>db</code>를 생성해주세요.</p>
<p>저는 <code>egudannaDB</code>라는 이름으로 생성해주었습니다.</p>
<br>

<h3 id="22-dbeaver에서-사용할-db-연결">2.2. DBeaver에서 사용할 db 연결</h3>
<p>이제는 생성한 <code>database</code>를 <code>DBeaver</code>에서 연결하는 작업을 시작하도록 하겠습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/3b23bcc6-333e-403a-8154-055dfec1b1d3/image.png" alt="">왼쪽상단의 버튼을 클릭합니다.<img src="https://velog.velcdn.com/images/1109_haeun/post/bb18e6c5-a696-4043-a3b4-01add5b042f1/image.png" alt=""><code>MariaDB</code>를 클릭합니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/92586da6-3142-4985-b953-65e004d08b01/image.png" alt="">알맞은 <code>port</code>번호와 <code>database</code>이름을 입력해줍니다.</p>
<p><code>port</code>번호에 따라 불러오는 <code>database</code>들이 달라지니 반드시 내가 여기서 사용할 <code>db</code>를 만들었던 <code>port</code>와 그 <code>db</code> 이름을 일치시켜 주세요.</p>
<p><code>password</code>는 <code>MariaDB</code>를 설치할 때의 비밀번호를 입력해주시고 왼쪽하단의 <code>Test Connection</code>을 눌러<img src="https://velog.velcdn.com/images/1109_haeun/post/942d5de0-ceee-498b-aef3-dd1cc539b8af/image.png" alt="">위의 메시지가 나오는지를 확인하세요. 그렇다면 연결에 성공한 것입니다!
<img src="https://velog.velcdn.com/images/1109_haeun/post/3e20bf56-1d82-4ecf-9b1d-4e263982ba7b/image.png" alt="">만약 위와 같은 창이 뜬다면 <code>Download</code>를 받은 뒤 다시 한 번 <code>Test Connection</code>을 실행해주세요.
<img src="https://velog.velcdn.com/images/1109_haeun/post/8d6dbd78-8154-4852-95cc-6794e1363718/image.png" alt="">연결에 성공하면 위와 같이 내가 사용할 <code>db</code>가 뜨며 관리가 가능해집니다.</p>
<p><code>port</code>번호도 <code>3308</code>로 올바른 것을 확인할 수 있네요!</p>
<br>

<hr>
<h2 id="3-springboot-프로젝트에-mariadb-연동">3. SpringBoot 프로젝트에 MariaDB 연동</h2>
<h3 id="31-applicationproperties에-mariadb-등록">3.1. application.properties에 mariaDB 등록</h3>
<p>설정파일에 아래와 같은 코드를 입력해주면 됩니다!
저는 <code>application.properties</code>를 기준으로 입력해주었습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/72e4a044-abac-471e-97b9-79a56ddec71b/image.png" alt=""></p>
<pre><code>spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://(IP주소):(port번호)/(사용할 db 이름)?serverTimezone=UTC&amp;characterEncoding=UTF-8
spring.datasource.username=(root와 같은 username)
spring.datasource.password=(해당 계정의 비밀번호)</code></pre><br>

<h3 id="32-연동-확인entity를-생성한-사람에-한함">3.2. 연동 확인(@Entity를 생성한 사람에 한함)</h3>
<p>저는 <code>SpringBoot</code>로 <code>domain(entity)</code>를 만든 상태였기에 <code>db</code>와 <code>springboot</code> 프로젝트가 연동이 잘 되었다면 <code>egudannadb</code>에 테이블이 잘 생성이 되어야 합니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/20221cf6-e7ef-475d-947e-1e71ec06d8fd/image.png" alt=""><code>DBeaver</code>에 접속하였더니 <code>videos</code>테이블이 잘 생성이 된 것을 확인할 수 있었습니다.</p>
<p>이렇게 <code>MariaDB</code>와 <code>SpringBoot</code>프로젝트 연동에 성공하였습니다!</p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Error][SpringBoot] @PathVariable값 누락 에러를 해결해보자]]></title>
            <link>https://velog.io/@1109_haeun/ErrorSpringBoot-PathVa</link>
            <guid>https://velog.io/@1109_haeun/ErrorSpringBoot-PathVa</guid>
            <pubDate>Sun, 24 Mar 2024 16:54:54 GMT</pubDate>
            <description><![CDATA[<h3 id="0325">0325</h3>
<hr>
<h2 id="✏️발생배경">✏️발생배경</h2>
<p><code>springboot</code>로 <code>rest api</code>를 만들던 중 하나의 <code>plan</code>데이터만 가져오는 <code>api</code>에서 <code>404</code> 에러가 나게 되었습니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/46a38b3a-4294-4691-b7e7-ecd76d297aa7/image.png" alt=""></p>
<p><code>postman</code>의 <code>console</code>에서는 아무런 문제가 보이지 않았기에 <code>intelliJ</code>의 <code>log</code>를 보니 아래와 같은 에러가 뜬 것을 확인할 수 있었습니다.</p>
<blockquote>
<p>Name for argument of type [long] not specified, and parameter name information not available via reflection. Ensure that the compiler uses the &#39;-parameters&#39; flag.</p>
</blockquote>
<blockquote>
<p>타입 [long]의 매개변수에 대한 이름이 지정되지 않았으며, 리플렉션을 통한 매개변수 이름 정보가 제공되지 않았습니다. 컴파일러가 &#39;-parameters&#39; 플래그를 사용하도록 설정되었는지 확인하십시오.&quot;</p>
</blockquote>
<br>

<hr>
<h2 id="✏️발생원인">✏️발생원인</h2>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/2834030a-77f3-40e3-acb7-06e7201decf2/image.png" alt="">원인은 간단했습니다.
엔드포인트에 정의된 <code>id</code>라는 변수를 인식하지 못해서 발생한 것이었습니다.</p>
<br>

<hr>
<h2 id="✏️문제해결">✏️문제해결</h2>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/31b3342d-5069-4912-8ef4-9b7ee211378e/image.png" alt=""><code>@PathVariable</code>이 엔드포인트의 변수를 인식할 수 있도록 <code>(&quot;id&quot;)</code>를 추가해주면 전달받은 매개변수의 값이 <code>id</code>에 잘 전달될 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/006fe13d-2eea-4275-a4fd-ff3350ff1c88/image.png" alt=""><code>postman</code>으로 확인해보니 하나의 <code>plan</code>데이터만 잘 받아와진 것을 확인할 수 있었습니다.</p>
<br>

<hr>
<h2 id="💡배운점">💡배운점</h2>
<p><code>@PathVariable</code>을 쓸 때에는 파라미터로 전달된 변수명을 알려주어야 한다는 것을 깨닫게 되었습니다.</p>
<p>같은 변수명이면 인식을 하는 줄 알았던 잘못된 지식을 바로 잡을 수 있었던 시간이었습니다.</p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Go] Golang으로 REST(CR) API를 만들어보자 ]]></title>
            <link>https://velog.io/@1109_haeun/Go-Golang%EC%9C%BC%EB%A1%9C-CRUD-API-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@1109_haeun/Go-Golang%EC%9C%BC%EB%A1%9C-CRUD-API-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 20 Mar 2024 07:10:16 GMT</pubDate>
            <description><![CDATA[<h3 id="0320">0320</h3>
<hr>
<h2 id="1-goland-세팅">1. <code>GoLand</code> 세팅</h2>
<h3 id="11-폴더-생성">1.1. 폴더 생성</h3>
<pre><code class="language-bash">mkdir bin</code></pre>
<ul>
<li>bin 폴더 생성<pre><code class="language-bash">mkdir pkg</code></pre>
</li>
<li>pkg 폴더 생성<pre><code class="language-bash">mkdir src</code></pre>
</li>
<li>src 폴더 생성</li>
</ul>
<p>위의 세 폴더 중 소스코드를 보관할 곳은 <code>src</code>입니다.
<code>src</code>안에는 소스를 작성할 <code>main.go</code>파일을 생성해주세요.</p>
<pre><code class="language-go">package main // package가 main으로 되어 있지 않다면 실행할 수가 없습니다. 

func main() {

}</code></pre>
<br>

<h3 id="12-go-module-등록">1.2. <code>go module</code> 등록</h3>
<pre><code class="language-bash">cd src
go mod init noah.io/ark/rest</code></pre>
<p><code>src</code> 폴더로 들어가 <code>go module</code>을 등록합니다. </p>
<br>

<h3 id="13-디렉토리-구조">1.3. 디렉토리 구조</h3>
<pre><code>root
├── bin
├── pkg 
├── src
    └── main.go
└── go.mod</code></pre><br>

<hr>
<h2 id="2-crud-api-생성">2. <code>CR(UD) api</code> 생성</h2>
<p><code>REST API</code>의 <code>CRUD</code> 중 <code>create</code>와 <code>read</code>의 작업만 하는 <code>REST API</code>를 만들어보겠습니다.</p>
<br>

<h3 id="21-서버-routing-해보기">2.1. 서버 <code>routing</code> 해보기</h3>
<pre><code class="language-go">package main

import &quot;net/http&quot;

func main() {
    http.HandleFunc(&quot;/&quot;, func(writer http.ResponseWriter, request *http.Request) {
        writer.Write([]byte(&quot;hello&quot;))
    })
    http.ListenAndServe(&quot;:8000&quot;, nil)
}</code></pre>
<p>위의 코드는 <code>go</code> 서버를 <code>localhost:8000</code>에 띄울 수 있게 해줍니다.</p>
<p><code>http.ListenAndServe(&quot;:8000&quot;, nil)</code>의 <code>nil</code>이란 값이 없음을 뜻합니다.
<code>ListenAndServe</code>의 두 번째 인수에는 실행할 핸들러를 적는데 값이 <code>nil</code>이기 때문에 맨 마지막에 정의된 핸들러가 실행되게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/1e16918d-0c5d-4e99-bd57-e6a1cb74cb5a/image.png" alt="">포스트맨에서 테스트를 하면 <code>hello</code>가 잘 출력되는 것을 확인할 수 있습니다.</p>
<br>

<h3 id="22-create-read-api-생성">2.2. <code>Create, Read api</code> 생성</h3>
<pre><code class="language-go">package main

import (
    &quot;encoding/json&quot;
    &quot;net/http&quot;
)

// 사용자의 이메일을 키로 정보를 저장하는 맵입니다.
var users = map[string]*User{}

// User 구조체
// 각각 nickname과 email의 타입이 문자열(string)입니다.
type User struct {
    Nickname string `json:&quot;nickname&quot;`
    Email    string `json:&quot;email&quot;`
}

func main() {
    http.HandleFunc(&quot;/users&quot;, func(writer http.ResponseWriter, request *http.Request) {
        switch request.Method {
        case http.MethodGet: // 조회
            json.NewEncoder(writer).Encode(users) // 인코딩
        case http.MethodPost: // 등록
            var user User
            json.NewDecoder(request.Body).Decode(&amp;user) // 디코딩

            users[user.Email] = &amp;user

            json.NewEncoder(writer).Encode(user) // 인코딩
        }
    })
    http.ListenAndServe(&quot;:8080&quot;, nil)
}</code></pre>
<p>위의 코드는 <code>localhost:8000/users</code>의 서버에 들어온 <code>request.Method</code>가 <code>get</code>인지 <code>post</code>인지를 판단하여 유저의 정보를 조회하거나 등록할 수 있는 <code>REST API</code>입니다.</p>
<p>만약 <code>request.Method</code>가 <code>http.MethodGet</code>이라면 <code>users</code>의 정보를 <code>json</code>형태로 변환하여 출력해줄 것입니다.</p>
<p>만약 <code>request.Method</code>가 <code>http.MethodPost</code>라면 <code>json</code>형식으로 들어온 <code>request</code>를 디코딩하여 <code>user</code>에 정보를 저장할 수 있도록 한 다음,</p>
<p>사용자의 <code>email</code>을 키로 사용하여 <code>user</code>의 정보를 저장한 뒤 다시 <code>json</code>형태로 정보를 인코딩하여 출력해줍니다.
<img src="https://velog.velcdn.com/images/1109_haeun/post/99c000ba-d302-46bf-970e-2b43fee47aff/image.png" alt="">포스트맨으로 실행하면 이렇게 값이 잘 나오는 것을 볼 수 있습니다.</p>
<br>

<hr>
<h2 id="3-middleware로-header에-content-type을-추가하기">3. <code>middleware</code>로 <code>header</code>에 <code>content-type</code>을 추가하기</h2>
<p>그런데 포스트맨의 <code>response</code>가 뭔가 이상합니다.
<code>json</code>형태이나 <code>text</code>로 출력이 되고 있습니다.</p>
<p>이는 <code>header</code>에 있는 <code>content-type</code>에 <code>application/json</code>을 등록해주지 않았기 때문입니다.
따라서 기본값인 <code>text</code>로 출력되는 것입니다.</p>
<p>이번에는 미들웨어를 활용하여 <code>response</code>의 <code>header</code>의 <code>content-type</code>을 <code>json</code>형식으로 추가해보겠습니다.</p>
<pre><code class="language-go">package main

import (
    &quot;encoding/json&quot;
    &quot;net/http&quot;
)

var users = map[string]*User{}

type User struct {
    Nickname string `json:&quot;nickname&quot;`
    Email    string `json:&quot;email&quot;`
}

// 4. 요청이 들어온 Response Header에 ContentType을 추가하고 전달받은 HandleFunc타입의 함수에 ResponseWriter와 Request를 넘겨줍니다.
// next는 미들웨어의 변수입니다. 
func jsonContentTypeMiddleware(next http.Handler) http.Handler {
    // 들어오는 요청의 Response Header에 Content-type을 json으로 설정해준다.
    return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Add(&quot;Content-Type&quot;, &quot;application/json&quot;)

        // 전달받은 http.Handler 호출
        next.ServeHTTP(writer, request)
    })
}

func main() {

    // 1. 새로운 mux를 만듭니다.
    // mux는 요청 경로와 핸들러를 매핑하는 역할을 합니다.
    mux := http.NewServeMux()

    // 2. 기존에 만들어놓은 HandleFunc를 HandlerFunc로 변경 (&quot;/users&quot; 삭제)
    // 원하는 경로를 함수와 연결시킬 수 있습니다.
    userHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        switch request.Method {
        case http.MethodGet: // 조회
            json.NewEncoder(writer).Encode(users)
        case http.MethodPost: // 삽입
            var user User
            json.NewDecoder(request.Body).Decode(&amp;user) // 디코딩

            users[user.Email] = &amp;user
            json.NewEncoder(writer).Encode(user)
        }
    })

    // 3. 만들어놓은 미들웨어에 파라미터로 넘깁니다. (&quot;/users&quot;는 이 때 사용)
    mux.Handle(&quot;/users&quot;, jsonContentTypeMiddleware(userHandler))
    http.ListenAndServe(&quot;:8000&quot;, mux)
}</code></pre>
<p>이렇게 <code>jsonContentTypeMiddleware()</code> 미들웨어로 요청이 들어온 <code>response</code>의 <code>header</code>에 <code>content-type</code>을 <code>application/json</code>으로 줌으로써 응답을 <code>json</code>형태로 볼 수 있게 됩니다. </p>
<p>아래는 포스트맨에서 확인한 응답 결과입니다. 
<img src="https://velog.velcdn.com/images/1109_haeun/post/f441a831-7793-40ea-9ede-c8eb065ed860/image.png" alt=""></p>
<br>

<hr>
<p>이렇게 간단한 <code>REST API</code>를 만들어보았는데
아무런 <code>db</code>를 연결하지 않아 <code>map</code>을 사용하였지만 다음에는 <code>mongodb</code>를 활용하여 값을 <code>db</code>에 저장해보겠습니다.</p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 우리가 google 웹페이지에 접속하기까지의 과정을 알아보자 (웹페이지 접속 과정)]]></title>
            <link>https://velog.io/@1109_haeun/CS-%EC%9A%B0%EB%A6%AC%EA%B0%80-google-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0%EA%B9%8C%EC%A7%80%EC%9D%98-%EA%B3%BC%EC%A0%95%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90.-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A0%91%EC%86%8D-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@1109_haeun/CS-%EC%9A%B0%EB%A6%AC%EA%B0%80-google-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0%EA%B9%8C%EC%A7%80%EC%9D%98-%EA%B3%BC%EC%A0%95%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90.-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A0%91%EC%86%8D-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Tue, 19 Mar 2024 01:10:07 GMT</pubDate>
            <description><![CDATA[<h3 id="0319">0319</h3>
<hr>
<p>이전 글에서 말했듯 모의 면접을 진행했을 때 이 질문이 나왔었지만 제대로 된 대답을 하지 못했습니다.</p>
<p>오늘은 우리가 <code>google.com</code>에 접속하여 <code>google</code>이 우리에게 웹페이지를 렌더링하기까지의 과정을 공부해보도록 하겠습니다.</p>
<br>

<hr>
<h3 id="1-사용자가-url을-웹-브라우저에-입력합니다">1. 사용자가 URL을 웹 브라우저에 입력합니다.</h3>
<p>사용자가 <code>google.com</code>을 웹 브라우저에 입력합니다.</p>
<p>서버, 휴대폰, 스마트 냉장고 등 인터넷의 각 디바이스에는 모두 <code>IP 주소</code>라는 고유한 주소가 있습니다. 
<code>IP 주소</code>에는 <code>3.34.220.186</code> 와 같은 <code>4</code>개의 번호가 매겨진 부분이 있으며 이를 통해 특정 사이트에 접속할 수 있습니다.</p>
<p>물론 <code>google.com</code>에 접속할 때에도 <code>IP</code>를 사용해야 하는데 <code>google.com</code>은 숫자가 아닌 것이 보이죠?</p>
<p><code>www.google.com</code>은 도메인 이름을 기반으로 서버에서 그에 맞는 <code>IP</code>주소를 찾아줍니다.
이는 우리가 숫자 <code>IP</code>를 하나하나 암기하여 칠 수고를 덜어주게 됩니다.</p>
<br>

<hr>
<h3 id="2-웹-브라우저는-입력한-url을-바탕으로-dns-서버에-연결할-ip를-요청합니다">2. 웹 브라우저는 입력한 URL을 바탕으로 DNS 서버에 연결할 IP를 요청합니다.</h3>
<p><code>google.com</code>을 주소창에 입력한 뒤 <code>enter</code>를 치면 웹 브라우저는 해당 도메인에 해당하는 <code>IP</code>주소를 찾아야 합니다.</p>
<p>입력한 <code>url</code>을 바탕으로 <code>DNS</code>서버에 연결할 <code>IP</code>를 요청하는 것인데 이 때 <code>DNS 조회</code>라는 작업을 사용합니다.</p>
<blockquote>
<p><strong>DNS(Domain Name System)란?</strong>
사람이 읽을 수 있는 도메인 이름(예: <a href="http://www.google.com)%EC%9D%84">www.google.com)을</a> 머신이 읽을 수 있는 IP 주소(예: 192.0.2.44)로 변환해주는 것</p>
</blockquote>
<br>

<hr>
<h3 id="3-dns-서버는-ip-주소를-웹-브라우저에게-응답으로-제공합니다">3. DNS 서버는 IP 주소를 웹 브라우저에게 응답으로 제공합니다.</h3>
<p><code>ISP</code>의 <code>DNS</code> 서버가 <code>DNS</code> 쿼리로 <code>www.google.com</code>을 호스팅하는 서버의 <code>IP</code> 주소를 찾습니다.</p>
<blockquote>
<p><strong>ISP란?</strong>
기업 네트워크 같은 인터넷 서비스 제공업체</p>
</blockquote>
<blockquote>
<p>*<em>쿼리란? *</em>
DNS 서버가 이름을 IP 주소로 변환하여 도메인 이름을 웹 브라우저에 입력할 때 최종 사용자를 어떤 서버에 연결할 것인지를 제어하는 요청</p>
</blockquote>
<br>

<hr>
<h3 id="4-웹-브라우저는-dns-서버에서-받은-ip를-통해-웹-서버와-tcpip-연결을-합니다">4. 웹 브라우저는 DNS 서버에서 받은 IP를 통해 웹 서버와 TCP/IP 연결을 합니다.</h3>
<p>웹 브라우저가 인터넷에서 서버를 찾으면 웹 서버와 TCP 연결을 설정하고, <code>HTTP</code>를 통해 평문 통신을 시작합니다.</p>
<blockquote>
<p><strong>TCP/IP(Transmission Control Protocol/Internet Protocol)란?</strong> 
컴퓨터가 서로 통신하는 경우, 특정 규칙이나 프로토콜을 사용하여 순서대로 데이터를 전송 및 수신할 수 있는데 이 때 전 세계적으로 가장 일상적으로 사용되는 프로토콜 세트 중 하나</p>
</blockquote>
<p>그런데 <code>HTTPS</code>를 사용하는 경우에는 <code>HTTPS</code>가 <code>HTTP</code>에서 보안이 더 좋아진 것이기 때문에 주고 받는 데이터의 암호화를 위한 <code>TLS (Transport Layer Security) 핸드셰이크</code>라는 추가 과정을 수행합니다.</p>
<blockquote>
<p><strong>TLS (Transport Layer Security) 핸드셰이크란?</strong>
암호화를 할 상호 대상을 확인하는 것</p>
</blockquote>
<br>

<hr>
<h3 id="5-웹-브라우저가-http-요청을-서버로-전송합니다">5. 웹 브라우저가 HTTP 요청을 서버로 전송합니다.</h3>
<p>서버가 요청을 처리하고 응답을 보낸다.
서버가 HTTP 응답을 보낸다.
응답은 웹 페이지와 필요한 리소스를 포함합니다.</p>
<p>웹 브라우저가 서버에 연결되면, 웹 브라우저가 페이지에 있는 각종 정보를 요청하기 위해 서버에 <code>HTTP</code> 요청을 전송합니다. </p>
<p><code>HTTP</code> 요청에는</p>
<ul>
<li>요청 라인<ul>
<li>브라우저(클라이언트)가 수행하려는 작업이 포함됨</li>
</ul>
</li>
<li>헤더(또는 요청에 대한 메타데이터)</li>
<li>본문<ul>
<li>요청의 맨 마지막 부분</li>
<li><code>GET</code>요청에 대해서는 비어있다. </li>
<li><code>POST</code> <code>PUT</code> <code>PATCH</code>와 같이 리소스를 조작하는 요청의 경우에는 생성 또는 업데이트할 클라이언트의 데이터가 여기에 포함된다.</li>
</ul>
</li>
</ul>
<p>이 세 가지가 있습니다.</p>
<p>또한 요청 라인에는 다음이 포함됩니다.</p>
<ul>
<li><code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, <code>DELETE</code> 또는 몇 가지 다른 <code>HTTP</code> 동사 중 하나인 요청 메서드</li>
<li>요청된 리소스를 가리키는 경로</li>
<li>통신할 <code>HTTP</code> 버전</li>
</ul>
<br>

<hr>
<h3 id="6-웹-서버는-받은-http-요청에-응답합니다">6. 웹 서버는 받은 HTTP 요청에 응답합니다.</h3>
<p>위에서 <code>HTTP</code> 요청을 보내면 웹 서버는 요청 라인, 헤더, 본문의 정보에 따라 응답하게 되며 응답에는 다음이 포함됩니다.</p>
<ul>
<li>*<em>상태 라인: *</em> 클라이언트에게 요청 상태를 알려준다.<ul>
<li><code>HTTP</code> 버전과 요청 상태의 숫자 및 텍스트 표현이 모두 포함된다.</li>
</ul>
</li>
<li>*<em>응답 헤더: *</em> 브라우저에게 응답 처리 방법을 알려준다.</li>
<li>*<em>리소스: *</em> HTML/CSS/JavaScript, image 파일과 같은 콘텐츠 데이터이다.</li>
</ul>
<br>

<p>아래는 <code>HTTP</code> 응답 예시입니다.</p>
<pre><code>HTTP/1.1 200 OK
Date: Tue, 25 May 2021 19:40:59 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
X-Powered-By: Express
Cache-Control: max-age=0, no-cache
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
X-Mod-Pagespeed: 1.13.35.2-0
Content-Encoding: br
Keep-Alive: timeout=1, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
    나머지 HTML 항목</code></pre><br>

<hr>
<h3 id="7-웹-브라우저는-받은-응답을-바탕으로-사용자에게-웹-페이지를-보여줍니다">7. 웹 브라우저는 받은 응답을 바탕으로 사용자에게 웹 페이지를 보여줍니다.</h3>
<p>드디어 브라우저가 사용자에게 웹 페이지를 렌더링하여 보여주게 됩니다.</p>
<p>위에서 <strong>응답 헤더</strong>라는 것이 브라우저에게 응답 처리 방법을 알려준다고 했는데 바로 이 응답 헤더를 검사하여 어떠한 타입의 리소스를 렌더링할지를 결정합니다.</p>
<p>만약 <code>Content-Type</code>헤더가 <code>HTML</code>이라면 <code>HTML</code>리소스를 렌더링할 것이고,
<code>image</code>라면 <code>image</code>파일들을 렌더링할 것입니다.</p>
<p>이러한 과정을 거쳐 지금 우리가 보고 있는 웹 페이지를 사용자에게 제공할 수 있는 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/341b17a4-17bb-45f1-8d4c-99a2a8215b45/image.png" alt=""></p>
<br>

<hr>
<p>이렇게 항상 우리가 봐왔던 <code>google</code> 웹페이지에 접속하기까지의 과정을 짚어보았는데 1초 남짓되는 시간동안 이렇게 다양한 요청과 응답이 있을 줄 몰랐습니다.</p>
<p>또 공부를 하는 동안 <code>TCP/IP</code> <code>TLS</code>등 모르는 용어가 상당히 많이 나와 <code>CS</code> 공부에 대한 경각심까지 일깨워진 것 같습니다.</p>
<p>오류가 있을 수 있습니다. 또 부족한 부분도 있을 것인데 알려주시면 감사하겠습니다.
지금까지 읽어주셔서 감사합니다.</p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] HTTP / HTTPS의 정의와 차이점을 알아보자 ]]></title>
            <link>https://velog.io/@1109_haeun/CS-HTTP-HTTPS%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@1109_haeun/CS-HTTP-HTTPS%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 17 Mar 2024 07:40:56 GMT</pubDate>
            <description><![CDATA[<h3 id="0317">0317</h3>
<hr>
<h2 id="0-개요">0. 개요</h2>
<p>2학년 중반, 졸업한 선배들과 함께하는 멘토링 중 모의면접 시간을 가졌습니다.</p>
<p>기술면접 부분에서 <code>http/https</code>의 차이점 및 구글로 접속하는 과정을 설명해보라는 등 많은 질문들이 있었으나 제대로 답변한 것은 거의 없다시피하였습니다.</p>
<p>3학년이 되어 회사면접을 준비해야하는 시기인 지금, <code>cs</code> 지식들을 하나하나 공부하며 기록할 생각입니다.</p>
<br>

<hr>
<h2 id="1-http">1. HTTP</h2>
<h3 id="11-http란">1.1. HTTP란?</h3>
<blockquote>
<p><strong>HTTP(HyperText Transfer Protocol)</strong>는 인터넷상에서 데이터를 전송하기 위한 프로토콜로, TCP/IP 4계층에서 응용 계층에 속합니다.</p>
</blockquote>
<br>

<h3 id="12-http의-특징">1.2. HTTP의 특징</h3>
<h4 id="121-비연결성connectionless">1.2.1. 비연결성<code>(connectionless)</code></h4>
<blockquote>
<p>클라이언트에서 요청을 보낸 후 서버로부터 응답을 받으면 연결을 끊는 것</p>
</blockquote>
<ul>
<li><p><strong>장점</strong>
비연결성은 불특정다수를 대상으로 하는 서비스에 유리합니다.</p>
<p>서버에서 응답을 받고 나서 연결을 계속 유지하려면 그만큼의 자원을 사용하여 비용이 많이 발생하게 되는데 비연결성은 자원을 아낄 수 있게 됩니다.</p>
</li>
</ul>
<br>

<ul>
<li><p><strong>단점</strong>
하지만 단점도 존재합니다.</p>
<p>연결을 유지하지 않는다는 특성 때문에 서버가 클라이언트를 기억할 수 없다는 단점이 있습니다.</p>
<p>또한 동일한 클라이언트가 연속으로 요청을 함에도 연결을 끊은 다음 다시 연결하는 연결과 연결 해제 과정을 반복하기 때문에 자원을 낭비할 수 있습니다.</p>
</li>
</ul>
<br>

<p>이러한 단점을 보완하기 위해 일정시간동안 연결을 유지할 수 있도록 <strong>HTTP Keep Alive</strong>를 사용합니다.</p>
<blockquote>
<p><strong>HTTP Keep Alive</strong>: HTTP 연결 시 일정 시간 동안 요청ㅇ을 유지할 수 있도록 사용하는 HTTP 헤더의 일종</p>
</blockquote>
<p>따라서 마지막 응답 이후 일정시간동안 연결을 유지하여 동일한 클라이언트일 경우 연결과 연결 해제 과정을 반복하지 않게 됩니다.</p>
<br>

<h4 id="122-무상태-stateless">1.2.2. 무상태 <code>(stateless)</code></h4>
<blockquote>
<p>서버에서 클라이언트의 상태를 저장하지 않는 것</p>
</blockquote>
<p>카페의 직원을 예로 들어보겠습니다.</p>
<ul>
<li>고객: 딸기 라떼 주세요.</li>
<li>직원: 네, 결제는 어떻게 하시겠어요?</li>
<li>고객: 카드로 할게요.</li>
<li>직원: 무슨 음료를 주문하시겠어요?</li>
</ul>
<p>위와 같이 하나의 질답이 끝났을 때 직원이 고객의 요청사항을 기억하지 못하면 고객은 한 번 주문할 때 <code>딸기 라떼 한 잔을 카드로 결제하겠습니다.</code> 라고 요청해주어야 합니다.</p>
<p>이렇게 서버에서 클라이언트의 요청을 저장하지 않는 것을 <strong>무상태</strong>라고 하며, 이 때 클라이언트는 요청에 필요한 데이터를 모두 가지고 있어야 하는데 이 방법들을 각각 <strong>쿠키(cookie)</strong>와 <strong>세션(session)</strong>이라고 합니다.</p>
<blockquote>
<p><strong>쿠키(cookie)</strong>: 클라이언트의 로컬 웹 브라우저에 저장하는 데이터 파일로, 키와 값을 저장합니다.</p>
</blockquote>
<ul>
<li>ex) 웹사이트의 로그인 정보, 온라인 쇼핑몰의 장바구니</li>
</ul>
<blockquote>
<p><strong>세션(session)</strong>: 서버에서 클라이언트와의 연결 정보를 저장 및 관리하는 것입니다. </p>
</blockquote>
<ul>
<li>장점: 서버에 데이터가 저장되어 쿠키보다는 보안이 뛰어남</li>
<li>단점: 접속자가 많을 경우 서버에 과부하가 걸릴 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/a0adbcd1-5036-48eb-a27c-a50b79a25763/image.png" alt=""><a href="https://hanamon.kr/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-http-http%EB%9E%80-%ED%8A%B9%EC%A7%95-%EB%AC%B4%EC%83%81%ED%83%9C-%EB%B9%84%EC%97%B0%EA%B2%B0%EC%84%B1/">출처 </a></p>
<br>

<hr>
<h2 id="2-https">2. HTTPS</h2>
<h3 id="21-https란">2.1. HTTPS란?</h3>
<blockquote>
<p><strong>HTTPS(HyperText Transfer Protocol Secure)</strong>는 보안 계층인 SSL/TLS를 이용해 HTTP의 보안을 강화한 웹 통신 프로토콜입니다.</p>
</blockquote>
<p>HTTP는 데이터 암호화를 거치지 않고 전송하기 때문에 보안에 취약한데 그래서 이를 보완한 HTTPS가 등장하였습니다.</p>
<br>

<h4 id="211-ssltls">2.1.1. SSL/TLS</h4>
<p><strong>SSL(Secure Socket Layer)</strong>은 넷스케이프(Netscape)에서 개발한 암호화 프로토콜입니다.</p>
<p>하지만 SSL은 당시 몇 가지의 문제점이 있었고 이를 보완해 나온 것이 바로 TLS입니다.</p>
<p>현재 HTTPS에서 통용되는 방식은 TLS이지만 SSL이라는 명칭이 아직 사라지지 않아 하나의 묶음으로 부르고 있습니다. </p>
<br>

<h3 id="22-https의-동작방식">2.2. HTTPS의 동작방식</h3>
<p><img src="https://velog.velcdn.com/images/1109_haeun/post/4c6cd1e4-e786-4bed-85f8-cda50e4b5056/image.png" alt=""></p>
<ul>
<li><a href="http://www.ktword.co.kr/test/view/view.php?no=3132">출처</a></li>
</ul>
<p>HTTPS의 동작 방식은 다음과 같습니다.</p>
<ol>
<li>데이터를 송신할 때 응용 계층에서 보안 계층의 SSL/TLS로 데이터를 보냅니다.</li>
<li>보안계층에서는 받은 데이터를 암호화하여 전송 계층으로 전달합니다.</li>
<li>그러면 데이터를 수신할 때 전송 계층에서 보낸 데이터를 보안 계층의 SSL/TLS에서 받아 복호화를 합니다.</li>
<li>복호화 후 데이터를 응용계층으로 보냅니다. </li>
</ol>
<br>

<hr>
<h2 id="3-http와-https의-차이점-정리">3. HTTP와 HTTPS의 차이점 (정리)</h2>
<blockquote>
<p>결론적으로 HTTP와 HTTPS의 차이점이라고 하면 보안 계층의 유무라고 볼 수 있습니다.</p>
</blockquote>
<p>HTTP는 데이터 암호화를 거치지 않고 전송하는 데에 반해 HTTPS는 SSL/TLS라는 보안 계층에서 암호화/복호화 작업을 거친 후 전송하기 때문입니다.</p>
<p>여기서 보안을 강화하기 위해 HTTPS가 등장했다는 사실도 다시 한 번 알 수 있는 것이죠.</p>
<br>

<hr>
<p>다음 포스팅에서는 위에서 간단히 다뤘던 HTTPS의 동작방식에서 더 나아간 웹 페이지 접속 과정을 google을 통해 다뤄보도록 하겠습니다.</p>
<p>읽어주셔서 감사합니다.</p>
<p>틀린부분은 알려주시면 감사하겠습니다.</p>
<br>

<hr>
]]></description>
        </item>
    </channel>
</rss>