<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>docu_a</title>
        <link>https://velog.io/</link>
        <description>a</description>
        <lastBuildDate>Mon, 24 Feb 2025 12:59:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. docu_a. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/docu_a" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[운영체제 | 인프런 운영체제 공룡책 강의 Chapter1-2. Intro & O/S Structures]]></title>
            <link>https://velog.io/@docu_a/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EA%B0%95%EC%9D%98-Chapter1-2.-Intro-OS-Structures</link>
            <guid>https://velog.io/@docu_a/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EA%B0%95%EC%9D%98-Chapter1-2.-Intro-OS-Structures</guid>
            <pubDate>Mon, 24 Feb 2025 12:59:51 GMT</pubDate>
            <description><![CDATA[<h1 id="01-운영체제가-뭐길래">01. 운영체제가 뭐길래?</h1>
<ul>
<li><p>OS (Operating System, 운영체제) : 컴퓨터(하드웨어) 시스템을 운영하는 소프트웨어</p>
</li>
<li><p>컴퓨터 : 정보를 처리하는 기계</p>
</li>
<li><p>정보 : 불확실한 상황을 측정해서 그것을 수치적으로 표현한 것</p>
</li>
<li><p>정보의 최소 단위 : bit (binary digit)</p>
<ul>
<li>8 bit (2^8) : 1 Byte</li>
<li>1024 bit (2^1024) : 1 MB</li>
</ul>
</li>
<li><p>정보의 처리 : 정보의 상태 변환 ( 0에서 1로, 1에서 0으로)</p>
<ul>
<li><p>부울 대수(Boolean Algebra): NOT, AND, OR (트랜지스터로 논리 게이트를 만들 수 있음)</p>
</li>
<li><p>논리게이트 : NOT, AND, OR, XOR, NAND, NOR</p>
</li>
<li><p>논리 회로 : IC, LSI, VLSI, ULSI, SoC, ... (게이트들을 회로에 배치할 수 있음)</p>
<ul>
<li>무어의 법칙 : 집적도가 1년 6개월에 2배씩 늘어남</li>
<li>황의 법칙 : 메모리가 1년에 두배씩 늘어남</li>
<li>메모리의 용량과 칩의 집적도가 기하급수적으로 늘어났음</li>
<li>이제는 더이상 집적하기 힘들어서 양자 컴퓨터에 대한 연구</li>
</ul>
</li>
</ul>
</li>
<li><p>정보의 저장: 플립-플롭</p>
</li>
<li><p>정보의 전송 : 데이터 버스, RF (Radio Frequency, 무선)</p>
</li>
<li><p>컴퓨터의 할아버지 (앨런튜링, Turing Machine)</p>
<ul>
<li>튜링 머신, 유니버설 튜링 머신, 헤드, 테이프</li>
<li>응용 프로그램, 운영체제, CPU, 메모리</li>
</ul>
</li>
<li><p>컴퓨터의 아버지 (폰노이만, Instruction Set Architecture)</p>
<ul>
<li>내장형 프로그램 방식(stored-program) 처음 도입</li>
<li>프로그램을 RAM에 저장할 수 있음</li>
<li>메모리에 있는 명령어를 fetch해와서 CPU에서 execute 하는 방식</li>
<li>명령어 집합으로 컴퓨터를 운영하는 방식</li>
<li>프로그램: 컴퓨터 하드웨어에게 어떤 특정 task를 실행시키는 명령어들의 집합</li>
</ul>
</li>
<li><p>운영체제</p>
<ul>
<li>컴퓨터 시스템을 운영하는 소프트웨어</li>
<li>운영체제는 컴퓨터에서 항상 실행 중인 프로그램</li>
<li>기본적으로 하드웨어 전체를 컨트롤하는 운영체제가 탑재가 돼있음</li>
<li>시스템 서비스를 애플리케이션 프로그램에게 제공해줌</li>
<li>운영체제가 해야할 일은 <strong>프로세스</strong>를 관리하고 리소스(파일, 프린터, input, output), 유저 인터페이스(마우스, 키보드) 등 관리해줘야함</li>
<li>H/W device &lt;- <strong>OS (window, linux, mac os)</strong> -&gt; App</li>
</ul>
</li>
</ul>
<h1 id="02-운영체제의-개념과-구조">02. 운영체제의 개념과 구조</h1>
<ul>
<li>전통적인 컴퓨터 시스템의 구조<ul>
<li>한 개 이상의 CPU</li>
<li>여러개의 device controller<ul>
<li>CPU</li>
<li>disk controller (disks, storage)</li>
<li>USB controller (mouse, keyboard, printer)</li>
<li>graphics adapter (monitor)</li>
</ul>
</li>
<li>공통 버스로 연결됨</li>
</ul>
</li>
<li>Bootstrap program<ul>
<li>컴퓨터 전원이 켜지자마자 처음으로 동작되어야할 ROM에 저장되어있는 프로그램은 메모리에다가 운영체제를 얹어주는 일을 해야함</li>
<li>HDD(하드디스크)에 있는 운영체제(특히 커널)를 메모리에 로딩해주는 역할을 해야함</li>
<li>그러면 나머지 응용 프로그램들을 메모리에 로드했다가 삭제했다가하는 거는 운영체제가 해줌</li>
</ul>
</li>
<li>Interrupts<ul>
<li>하드웨어가 버스를 통해서 언제라도 interrupter를 트리거 시킬 수 있음</li>
<li>트리거를 시키면 cpu에 시그널을 보내줌</li>
<li>키보드에서 &quot;A&quot;라는 키를 눌렀다는 것을 CPU한테 알려줄때 interrupt라는 방법으로 알려줌</li>
</ul>
</li>
<li>폰노이만 아키텍처 (복습)<ul>
<li>컴퓨터에 내릴 수 있는 명령을 명령의 집합으로 정의하고, 이 명령어 집합으로 구성된 컴퓨터 프로그램을 메모리에 로딩하면 메모리에 있는 명령어들을 cpu가 하나씩 fetch를 하고 execute를 함</li>
<li>A typical instruction-execution cycle<ul>
<li>first <strong>fetches</strong> and instruction from memory</li>
<li>and stores that instruction in the <strong>instruction register</strong> (명령어를 메모리에서 하나씩 가져오는 레지스터 나중에 나옴)</li>
</ul>
</li>
</ul>
</li>
<li>Storage System<ul>
<li>CPU가 있고, memory가 있고 비휘발성 장치인 Storage system이 있음</li>
<li>storage system의 계층 구조 (용량에 따른, Access time에 따른)<ul>
<li>register : cpu 안의 회로에 묶여있어 가장 빠름</li>
<li>cache : cpu와 ram 사이의 cache memory. 빠르지만 RAM보다 훨씬 비싸기 때문에 용량을 크게 할 수 없음</li>
<li>main memory : 흔히 말하는 RAM</li>
<li>solid-state-disk (SSD) : 메모리 형태의 하드 디스크</li>
<li>hard disk (HDD) : 마그네틱 카드 자기장을 이용</li>
<li>optical disk : HDD로 저장하기에도 양이 많은 것들 저장, 백업용도</li>
<li>magnetic tapes : 백업 용도 (은행 등 보존연안 10년 같은)</li>
</ul>
</li>
<li>I/O Structure<ul>
<li>실행 : Thread of Execution<ul>
<li>CPU가 thread of execution을 가지고 있는데 캐시를 통해서 RAM에 액세스를 하고 IO device가 interrupt를 걸어주고, 데이터를 주고 받고, IO request를 보내고 이런 복잡한 IO structure를 가지고 있음</li>
<li>유투브 동영상을 보여주는거: 네트워크로부터 데이터를 받는거는 network device가 할일이고, 그 다음에 LCD에 화면 display하는 것은 LCD display가 할일. CPU가 할일은 정지 이런 작업 정도. 이런 경우에는 네트워크 카드가 다이렉트로 LCD에 데이터를 보냄. 이렇게 디바이스끼리 다이렉트로 액세스 하는것 = Direct Memory Access (DMA)</li>
<li>커널이 업데이트 될일은 거의 없음. 현재 운영체제의 90% 이상은 커널에 붙은 디바이스 컨트롤러를 만드는 일.</li>
</ul>
</li>
</ul>
</li>
<li>컴퓨터 시스템 요소의 정의<ul>
<li>CPU, Processor, Core, Multicore, Multiprocessor<ul>
<li>예전에 있었던 cpu 하나에 메모리 하나있는 구조는 더이상 사용하지 않음.</li>
<li>최근 : Symmetric multiprocessing (SMP)</li>
<li>프로세서, cpu가 한 개가 아니라 멀티라는 것</li>
<li>메모리가 한개인데 cpu0, cpu1, cpu2 이런식으로 여러개의 cpu가 각각의 레지스터와 캐시를 갖고 붙어있는 형식</li>
<li>Multi-core : 같은 프로세서 칩 하나에 여러 개의 코어가 붙어 있는 것. cpu를 한장만(마더보드가 한개) 꼽지만 그 안에 코어가 여러개 (듀얼-2개, 쿼드-4개, ...)</li>
<li>Multiprogramming : 하나의 프로그램이 한 번에 한 개 이상 돌고 있는 것. 메모리에 여러 개의 프로세스가 동시에 올라가 있는 것. CPU 사용 효율을 높일 수 있음.</li>
<li>Multitasking(=multiprocessing) : 하나의 CPU가 실행 속도가 빠를때 여러개의 job(processor)을 자주 바꿔줌. </li>
<li>CPU Scheduling : RAM에 여러개의 프로세스를 동시에 실행. CPU는 한개. 어떤 프로세스를 다음에 실행시킬 지의 결정.</li>
</ul>
</li>
</ul>
</li>
<li>user mode &amp; kernel mode<ul>
<li>user mode : 프로세스 실행 중에 시스템콜을함<ul>
<li>시스템콜 : OS한테 어떤 서비스를 요청함</li>
</ul>
</li>
<li>kernel mode: 시스템 콜을 하면 그때 커널 모드로 바껴서 커널 모드에서 시스템콜을 처리하고 난 다음에 다시 user mode로 되돌아감. kernal mode가 아니면 직접적으로 하드웨거 제어가 불가능.</li>
</ul>
</li>
<li>Virtualization (가상화)<ul>
<li>Virtual Machine Monitor (VMM) 을 끼면 한 컴퓨터에서 여러 운영체제를 실행시킬 수 있음. cpu 스케줄링을 하듯이 os vmm 스케줄링 가능.</li>
</ul>
</li>
<li>OS 인터페이스<ul>
<li>cli : sh, bash, tcsh, zsh ...</li>
<li>GUI : Windows, Aqua for MacOS, KDE/GNOME for Linux ...</li>
<li>touch-screen interface : android UI, iPhone UI ...</li>
</ul>
</li>
<li>실제 응용프로그램이 OS와 상호작용하는 방식<ul>
<li>시스템 콜<ul>
<li>OS가 제공해주는 서비스들을 시스템 콜을 하면됨</li>
<li>read, write ...</li>
<li>OS의 API 콜이 시스템 콜이라고 이해하면 됨</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p> 인프런 <a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98">운영체제 공룡책 강의, 주니온</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git]]></title>
            <link>https://velog.io/@docu_a/Git-6suprug1</link>
            <guid>https://velog.io/@docu_a/Git-6suprug1</guid>
            <pubDate>Mon, 12 Aug 2024 12:19:38 GMT</pubDate>
            <description><![CDATA[<h1 id="git-config">Git config</h1>
<pre><code class="language-bash">git config --list
git config --global user.name &lt;github-name&gt;
git config --global user.email &lt;email&gt;
cat .git/config</code></pre>
<h1 id="local-repo">Local Repo</h1>
<pre><code class="language-bash">git init   #.git 디렉터리 생성
vi .gitignore
echo &quot;작성&quot; &gt; text.txt
git ls-files # text.txt x

git add .
git ls-files # text.txt o (tracked)
git status

git commit -m &quot;commit msg&quot;
git log   #commit logs
git status  # staging 상태 확인
</code></pre>
<ul>
<li>파일 수정 → modified(WD) → <strong>add</strong> → modified(Stage) → <strong>commit</strong> → unmodified(Repo)</li>
</ul>
<h1 id="remote-repo">Remote Repo</h1>
<pre><code class="language-bash">git remote add origin &lt;remote url&gt;
git branch -M master # main -&gt; master
git push -u origin master
git push</code></pre>
<pre><code class="language-bash">git remote rename &lt;before&gt; &lt;after&gt; # 별칭 변경
git remote rm origin # origin 저장소 연결 해제
git (push | pull) -u origin &lt;branch&gt;
git remote show origin # origin 정보 보기

git diff HEAD~1 # 이전 커밋과의 diff</code></pre>
<h3 id="restoreunstage">restore(unstage)</h3>
<pre><code class="language-bash">git add .
git status

git restore --staged text.txt
git status
git ls-files
git restore text.txt  # 수정전 상태로 돌아옴</code></pre>
<h3 id="rm-untracked로-만들기">rm (untracked로 만들기)</h3>
<pre><code class="language-bash">git log  #commit log
git log --raw  #with filename
git log --graph  #git log --oneline
git reflog --raw  #with HEAD pointer

git rm --cache text.txt #원본은 놔두고 cache에서만 삭제
git status
git ls-files

git rm text.txt  # --cache가 없으면 완전삭제!!
restore --stage  #restore</code></pre>
<h1 id="git-branch">Git Branch</h1>
<pre><code class="language-bash">git branch -a
git push origin &lt;branch&gt;
git push -u origin &lt;branch&gt; # -u는 추적관계 설정.
# git push나 pull만 입력해도 자동으로 푸시해줌 (origin &lt;branch&gt;생략)

git remote -v # 원격 저장소 링크
git remote show origin
git push origin &lt;orgname&gt;:&lt;newname&gt;
git clone -b &lt;branch&gt; &lt;remote-url&gt; &lt;filename&gt;
git checkout -t origin/&lt;branch&gt; # 원격 브랜치 가져오기
git checkout --track origin/&lt;branch&gt;</code></pre>
<h3 id="branch-삭제">branch 삭제</h3>
<pre><code class="language-bash">git branch -d &lt;branch&gt;
git branch -D &lt;branch&gt; #merge, commit 남았을때 강제 삭제

git push origin --delete &lt;branch&gt;
git push origin -d &lt;branch&gt;
git fetch -p</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[docker-compose postgres "password authentication failed for user" 에러]]></title>
            <link>https://velog.io/@docu_a/docker-compose-postgres-password-authentication-failed-for-user-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@docu_a/docker-compose-postgres-password-authentication-failed-for-user-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Sun, 05 May 2024 05:31:58 GMT</pubDate>
            <description><![CDATA[<p>외주 작업을 하던 중 ec2에 프리즈마 마이그레이션을 하는데 에러를 발견했다.
에러를 수정하는데에 거의 6시간 넘게 고생해서 혹시 누군가에게 도움이 될까 하는 마음에 글을 남긴다.</p>
<p>이 전에 나는 한 번</p>
<pre><code class="language-shell">sudo docker-compose up -d</code></pre>
<p>를 실행한 상태였는데 </p>
<pre><code class="language-shell">sudo docker-compose config</code></pre>
<p>로 컨피그를 확인했을때 내 env에 띄어쓰기가 포함돼있어서 아이디와 비밀번호에 띄어쓰기가 들어가 있어서 디비에 접근할 수가 없었다.
그래서 지금 비슷한 문제가 있다면 우선 env파일에 띄어쓰기가 포함돼있는지를 확인하고 수정하는 것이 좋다.</p>
<pre><code class="language-shell">sudo docker-compose down</code></pre>
<p>을 하고</p>
<pre><code class="language-shell">sudo docker-compose up --build -d   #이미지를 새로 빌드하여 컨테이너를 실행</code></pre>
<p>다시 컨피그를 확인했는데 변경사항이 적용되어 정상적으로 동작할 줄 알았으나 여전히 db에 접근할 수 없었다.</p>
<p><img src="https://velog.velcdn.com/images/docu_a/post/b5fb1e05-51a3-47f2-8dcd-58a5294badd2/image.png" alt=""></p>
<p> 일단 이 에러는 프리즈마 에러로 데이터베이스 url에 포함된 데이터베이스 인증정보가 틀렸을때 났던 에러다.</p>
<pre><code class="language-shell">sudo docker-compose logs postgres    # Docker Compose를 사용하여 실행된 postgres 컨테이너의 로그를 출력하는 명령어</code></pre>
<p><img src="https://velog.velcdn.com/images/docu_a/post/703e775c-b491-4867-9bee-55a8bafa67d7/image.png" alt=""></p>
<p>이렇게 Role &quot;user&quot;(지정한 user명) does not exist 이런 메시지가 나타났다.</p>
<p>Role &quot;user&quot;(지정한 user명) does not exist 나 위의 프리즈마 에러로 계속 검색하던 중
비밀번호를 만들어야한다, POSTGUS이런 변수를 env에 추가해야한다, psql에서 create user를 해야한다 등 많은 답변을 발견했는데 내 에러 케이스에는 해당하지 않았고 찾은 방법은 간단했다.</p>
<p><a href="https://www.doitdjango.com/board/qna/36/">https://www.doitdjango.com/board/qna/36/</a>
<a href="https://stackoverflow.com/a/67265613/24519580">https://stackoverflow.com/a/67265613/24519580</a></p>
<p>✨ 위 사이트들에서 확인할 수 있었는데 ✨</p>
<pre><code class="language-shell">sudo docker-compose down -v </code></pre>
<p>도커 컨테이너를 처음에 세팅할때 아이디, 비밀번호를 잘못 세팅하면 볼륨을 없애고 다시 생성하지 않는 이상 그 세팅이 유지된다는 것이었다.</p>
<p><img src="https://velog.velcdn.com/images/docu_a/post/77f6134b-80c2-401f-a622-774793a9fc18/image.png" alt=""></p>
<p>이렇게 하고나니 두번째 줄의 Volume removed가 기존에 그냥 down만 했을때에는 없었는데 새로 생겼다.</p>
<p>그리고 다시 빌드해서 실행하니 정상적으로 마이그레이션이 동작했다.</p>
<p>도커를 처음 사용해보는 거였는데 잘 알지 못하고 써서 고생을 했던 것 같다. 반나절동안 간단한 해결방법이 있는 이슈로 고생했지만 그래도 많이 배운 좋은 경험이었다 : ) </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | [실전프로젝트] Redis Stream을 활용한 알림 기능 구현기]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Redis-Stream%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%95%8C%EB%A6%BC-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%EA%B8%B0</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Redis-Stream%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%95%8C%EB%A6%BC-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%EA%B8%B0</guid>
            <pubDate>Mon, 15 May 2023 10:32:20 GMT</pubDate>
            <description><![CDATA[<h2 id="기능-구현에-앞서">기능 구현에 앞서</h2>
<p>프로젝트에서 구현하고자 했던 알림 기능은 웹푸시 알림이 아닌 쪽지처럼 쌓이는 형태의 알림이었다. 웹푸시 알림에 대한 정보는 많았지만 이런 알림은 찾으면서도 자료가 많이 없어서 많이 힘들었다. 검색해보면서 pub/sub, kafka, socket, stream 등 알림에 쓰일만한 기능 키워드들을 우선 탐색해보았다.</p>
<p><code>redis pubsub</code>
&#39;pub/sub으로 알림 기능을 구현할 수 있다&#39;까지 설명된 블로그는 많았으나 사실 실제 코드로 나타난 자료가 없어서 잘 와닿지 않았다. pub/sub + socket 조합은 알림을 전달하는 방식인 것 같고 저장되는 방식은 아니라고 이해했다. 전송한 메시지의 내용을 저장하지 않고, 클라이언트에게 전달한 후 이를 바로 삭제한다는 점이 쌓이는 형태의 알림창인 우리의 기능과는 다르다고 생각했다.</p>
<p><code>kafka</code>
아파치 카프카는 대규모 분산 시스템에서 데이터를 처리하는 데 특화된 기술이라고 한다. 큰 규모의 서비스에서 쓰이는 것 같았고 이미 이메일 인증을 레디스로 하고 있어서 레디스로 마음이 더 기울게 됐다.</p>
<p><code>redis stream</code>
<a href="http://redisgate.kr/redis/command/streams.php">http://redisgate.kr/redis/command/streams.php</a> 
이 자료를 처음보고 소비자 그룹의 개념과 xreadgroup, xpending 커맨드를 봤을때 이걸로 알림 기능이 구현 가능할 것 같다는 생각이 들었다.
stream + SSE/폴링 <a href="https://velog.io/@saeeng/SSE-Server-Sent-Event">https://velog.io/@saeeng/SSE-Server-Sent-Event</a> 방식으로 알림 기능을 구현해보고자 했다.</p>
<p>내가 필요한 기능이 구현된 자료가 거의 없었고 한 기능에만 오래 매달릴 수 없는 상황이여서 사실 마음에 제일 끌리는것에 부딪혀보는게 최선이었다.</p>
<h2 id="구현-방법-✅">구현 방법 ✅</h2>
<p><img src="https://velog.velcdn.com/images/docu_a/post/fda6ade7-b007-4a1b-9d17-03179e74db0a/image.png" alt=""> <code>createStream</code> : 회원가입 시, id 별로 stream key 생성 [(코드)] (<a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/notification.repository.js#L87">https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/notification.repository.js#L87</a>) ☛ 회원가입api, 소셜로그인 strategy에서 import해서 사용
<code>saveToStream</code> : 알림 저장 시, 알림 받을 회원의 스트림에 생성시간 타임스탬프가 id인 엔트리를 추가하고 XREADGROUP 호출하여 consumer(알림받을 회원)에게 할당<a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/notification.repository.js#L108">(코드)</a>☛ 아트그램/전시 좋아요, 댓글, 답글 작성하는 메서드에서 import해서 사용
<code>confirmNoti</code>  : 알림 조회시, XACK 처리 <a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/notification.repository.js#L71">(코드)</a> ☛ PATCH /notification API
<code>getNotiList</code>  : 알림 목록 제공 시, XPENDING으로 읽지 않은 알림과 읽은 알림을 구분한 데이터를 XREVRANGE로 최신순으로 제공<a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/notification.repository.js#L25">(코드)</a> ☛ GET /notification API</p>
<h2 id="도전">도전</h2>
<h3 id="--stream-x-command-관련-오류">- stream x command 관련 오류</h3>
<p>우선 터미널로 기본적인 스트림 커맨드를 연습해봤고 vscode로 redis를 연결하는 것부터 정말 어려웠다. 원하는대로 쳤는데 stream 명령어를 인식하지 못하는 것 같았다. stream은 redis 5.0이상에서 지원되었기 때문에 ioredis를 추가적으로 사용했고 redis-server의 버전도 최신버전으로 설치해서 문제를 해결할 수 있었다.</p>
<h3 id="--참고자료의-부족">- 참고자료의 부족</h3>
<p>웹푸시 형식의 알림 기능에 대한 자료는 많았지만 프로젝트에서 의도한 형식의 알림기능은 참고할만한 자료가 정말 없었다. stream에 대한 자료는 정말 없었고 있어도 &#39;pub/sub을 알림기능에 쓸수있다~&#39; 정도의 자료만 있었고 코드구현한 예시를 참고하기 어려워서 며칠동안 머리를 싸맸던 기억이 있다. 커맨드 하나하나를 어떻게 쓸지 생각하고 고민하는게 도전이었다.</p>
<h2 id="아쉬운-점">아쉬운 점</h2>
<h3 id="--실시간-알림을-구현하지-못한-것">- 실시간 알림을 구현하지 못한 것</h3>
<p>SSE 혹은 polling 방식으로 실시간으로 알림을 받아보는 것까지가 나의 목표였는데 굳이 실시간 알림까지는 필요없을 것 같고 마이페이지에 들어갔을때 확인되는 정도이면 될 것 같다는 의견이 다수여서 구현해보지 못한게 조금 아쉬웠다. sse까지 적용됐어야 redis stream의 실시간성이나 빠른 조회가 가능하다는 장점을 더 살려볼 수 있었을 것 같다.</p>
<h3 id="--알림이-많이-쌓였을-시의-삭제-방식에-대한-고민이-끝나지-않았다">- 알림이 많이 쌓였을 시의 삭제 방식에 대한 고민이 끝나지 않았다</h3>
<p>결국 사용자가 늘어나고 알림을 무수히 쌓이게 될것이다. 무한하게 알림을 다 저장할 수는 없다. 이때 어떤 기준으로 알림을 삭제할까? 예를들어 1인당 600개까지만 알림이 저장되도록 혹은 일자를 기준으로 3개월 정도의 알림 데이터만 저장한다? 지금 전시나 아트그램 게시글도 soft delete방식을 채택하는데 이런 데이터도 결국 언젠간 삭제가 돼야할텐데 어떤 기준으로 삭제를 진행해야하는지에 대한 고민이 아직 있다.</p>
<h3 id="--알림-백업-필요성">- 알림 백업 필요성</h3>
<p>사실 레디스 자체에서 디스크에 저장하는 방식이 기본적으로 있어서 내가 서버를 껐다 켜도 자료가 그대로 남아있어서 나는 알림 백업의 필요성에 대해서 크게 인지를 하지 못했다. 그치만 기술매니저님께서 백업의 필요성을 많이 강조하셨고 스케줄러를 사용해서 알림을 백업할 수 있다는 사실까지 알게 되었다.</p>
<h3 id="--알림창-무한스크롤">- 알림창 무한스크롤</h3>
<p>사실 메모리 기반의 데이터베이스라 그런지 조회해오는 속도가 느리지 않아서 그런가 무한스크롤의 필요성을 딱히 느끼지는 못하긴 했다. sequelize는 limit,offset이 있지만 stream은 직접 갯수를 지정해서 가져오는 방식 이외에는 limit, offset처럼 구현할 방법이 떠오르지 않았다. 소셜로그인 마무리 작업에 시간이 없어서 여기까지는 있으면 좋겠다까지만 생각하고 깊게 고민해보지 못했지만 다음에 비슷한 기능을 구현하게 된다면 꼭 염두해둬야할 부분일 것 같아 기록해본다.</p>
<h2 id="느낀-점">느낀 점</h2>
<h3 id="--뭐든-도전해볼-수-있을-것-같은-용기">- 뭐든 도전해볼 수 있을 것 같은 용기</h3>
<p>그래도 혼자 redis stream으로 알림을 구현해보면서 자료들을 그대로 옮기거나 하는게 아니라 내가 직접 하나하나 만들어가는 경험들을 해보면서 많이 성장했고 내가 직접 하나의 기능을 처음부터 하나하나 완성해나가본 경험이 정말 소중했던 것 같다. 아직 반쪽짜리 기능인 것 같아 아쉬움이 있지만 그래도 앞으로 다른 기능 구현을 함에 있어서 자료가 없어도 뭐든 도전해볼 수 있을 것 같은 용기가 생긴 것 같다.</p>
<h3 id="--여전히-의문은-있다">- 여전히 의문은 있다.</h3>
<p>redis stream을 사용한게 최선의 선택이었을까? 그냥 mysql로 구현을 했다면? 사실 redis에 stream 형식으로 저장하니 id가 key값이 되어 저장이돼서 훨씬더 자료가 보기 좋고 조회하기 쉽게 저장이 된 것같고 만약에 notification이라는 mysql 테이블에 알림을 무수히 쌓았다면 입력 데이터가 많아질수록 조회 속도에서 차이가 있지 않을까라는 생각이 있다. 그치만 mysql에 저장한다면 백업의 필요성에 대해서는 고려하지 않아도 되고 무한스크롤 또한 쉽게 구현했을 수 있었을 것 같다.</p>
<h2 id="배운-점">배운 점</h2>
<p><strong>- redis의 stream 커맨드 사용법</strong>
<strong>- 알림 기능은 정말 고려할 부분이 많은 기능이구나</strong></p>
<h2 id="완성본">완성본</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/docu_a/post/6aa0e3e1-9e9d-4335-835d-0e1609401347/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/0ac6a19e-5893-4c55-9d92-b1f3ecc7aa75/image.png" alt=""></th>
</tr>
</thead>
</table>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | [실전프로젝트] passport를 사용한 OAuth 인증 기반 소셜로그인]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-passport%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-OAuth-%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EB%B0%98-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-passport%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-OAuth-%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EB%B0%98-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Mon, 15 May 2023 10:31:20 GMT</pubDate>
            <description><![CDATA[<h2 id="구현한-기능">구현한 기능</h2>
<p>passport를 사용한 OAuth 인증 기반 소셜로그인</p>
<h2 id="구현-방법">구현 방법</h2>
<ol>
<li>/passport에 각 소셜로그인 서비스의 OAuth2 인증 전략을 설정</li>
<li>로그인 라우터 생성</li>
<li>콜백 라우터 생성</li>
<li>사용자 정보 저장</li>
<li>서버자체 토큰 발급</li>
</ol>
<h2 id="도전">도전</h2>
<h3 id="express-session">express-session</h3>
<p>express-session을 사용하지 않은 상태로 passport를 적용시켰더니 session을 사용해야한다는 에러 메시지가 났다. 
마이페이지 api와 알림 기능 구현 전이여서 소셜로그인에 많이 할애할 시간이 없었고 찾아보니  express-session 개념이 생각보다 방대했다. passport도 처음 써보는데 express-session까지 처음 배우는 개념을 적용하면 에러를 처리할때 고려하지 못할 변수들이 많을 것 같았다. 제대로 이 개념을 이해하고 넘어가지 못한다면 과감히 생략하는게 낫다고 판단했다. 
찾아보니 passport가 업데이트되면서 session이 적용돼야 가능했고 그 이전 버전을 이미 사용한 사람들이 같은 고민을 했던 것 같다. <a href="https://github.com/jaredhanson/passport/issues/939#issuecomment-1386091843">자료</a>를 참고로 express-session 적용은 하지 않았다. </p>
<h3 id="http-proxy-middleware">http-proxy-middleware</h3>
<h4 id="문제-프론트와-백-도메인이-달라서-passport-전략-실행-후-콜백-라우터에서-서버자체토큰을-발급해서-쿠키값에-담아-프론트주소로-redirect하면-쿠키값이-안담기는-현상-발생"><strong>[문제]</strong> 프론트와 백 도메인이 달라서 passport 전략 실행 후 콜백 라우터에서 서버자체토큰을 발급해서 쿠키값에 담아 프론트주소로 redirect하면 쿠키값이 안담기는 현상 발생</h4>
<h4 id="해결방안"><strong>[해결방안]</strong></h4>
<ol>
<li>쿼리스트링에 토큰값을 담아 프론트에 보낸다. 쿼리스트링으로 보내면 보안관련한 이슈가 있을 수 있지만 레디스에 토큰값을 임시저장하고 token_key만 쿼리스트링으로 보낸다면 극복가능하다 판단</li>
<li>http-proxy-middleware로 프록시를 설정해서 cors 정책을 우회한다<ol start="3">
<li>nginx에 프론트 서버를 배포한다</li>
</ol>
</li>
</ol>
<h4 id="2번-선택-이유"><strong>[2번 선택 이유]</strong></h4>
<pre><code> - 쿼리스트링으로 토큰값을 보내면 보안상 안전하지 못할 수 있고 http-proxy-middleware를 사용하는것이 수월할 수 있다는 기술매니저님의 조언
- 프론트는 버셀에서 자동배포와 ssl 인증을 적용할 예정이었기 때문에 최대한 프론트는 계획한 예정대로 진행되게 하고 싶었음
- 프록시를 설정해서 cors 정책을 우회하면 프론트에서 추가적으로 쿼리스트링값을 처리해야할 필요가 없어서 작업량이 줄 것이라 생각</code></pre><h4 id="2번-실행-http-proxy-middleware-적용"><strong>[2번 실행-http-proxy-middleware 적용]</strong></h4>
<ul>
<li>controllers/user.controller.js <a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/controllers/user.controller.js#L33">코드</a></li>
<li>app.js <a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/app.js#L174">코드</a></li>
</ul>
<h4 id="추후-추가적으로-발생한-문제"><strong>[추후 추가적으로 발생한 문제]</strong></h4>
<p>  프록시 설정 후 /exhibition, /mypage, /artgram처럼 백엔드 api uri와 프론트엔드 주소가 겹칠 경우 새로고침 시 json값이 보이는 상황 발생</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/docu_a/post/7324cfe7-8b82-4ce3-8bdb-e97ce5dc45ab/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/691a9373-0c33-4f66-ac4b-033c23184161/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/158a69d2-dbaf-48f4-b0f1-98a1e959461d/image.png" alt=""></th>
</tr>
</thead>
</table>
<h4 id="해결"><strong>[해결]</strong></h4>
<p>  백엔드 api uri 앞에 /api를 추가하여 겹치지 않게 설정함</p>
<h2 id="아쉬운점">아쉬운점</h2>
<ul>
<li>이용자 식별자 사용의 필요성
유저테스트를 할때는 그런 문제가 없었지만 팀에서 테스트를 하면서 여러계정으로 가입했는데 네이버로 이메일인증 회원가입을 한 사람이 다시 네이버 로그인을 시도했을때 오류가 나는 현상이 발생했다. 이 문제에 대한 실마리를 네이버 소셜로그인 앱 허가를 받으면서 얻을 수 있었는데<div><img src = "https://velog.velcdn.com/images/docu_a/post/5defdd34-6102-4264-b46d-d8e498b3441b/image.png" width="30%"></div>
카카오 회원가입시에 id값을 사용했던 것처럼 이메일이 아닌 이용자식별자를 사용해서 가입을 했어야함을 깨달았다. 지금은 rds를 private으로 설정해두셔서 회원가입을 테스트해보기 어려운 상태여서 해결을 못했지만 다음번에 소셜로그인 구현시에는 더 완벽하게 기능 구현을 할 수 있을 것 같다.  



</li>
</ul>
<h2 id="완성본">완성본</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/docu_a/post/76e56f43-f2ef-41ed-9c78-56f327f59968/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/414dcc22-23a9-4f5d-8bae-0700a24eb2e9/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/fae9c714-557d-414d-95d6-fbe8d4d2c9b7/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td><div><img src="https://velog.velcdn.com/images/docu_a/post/f7c4cadc-de0b-4032-9215-a9c0d885f143/image.png" width="50%"></div></td>
<td></td>
<td></td>
</tr>
<tr>
<td>회원가입이나 로그인은 무심코 지나갈 수 있는 부분이었는데 알아봐주시고 좋은 피드백들을 많이 주셔서 감사했다.</td>
<td></td>
<td></td>
</tr>
<tr>
<td>소셜로그인 기능을 구현하기까지 우여곡절도 마음고생도 많았는데 아쉬운 부분은 여전히 있지만 그래도 기능구현하기까지의 작은 목표들을 달성하는 순간순간들에는 정말 행복했던 것 같다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://inpa.tistory.com/491">🌐 OAuth 2.0 개념 - 그림으로 이해하기 쉽게 설명</a>
<a href="https://inpa.tistory.com/488">[NODE] 📚 카카오 로그인 (passport-kakao) ✈️ 구현</a>
<a href="https://lakelouise.tistory.com/233">[Node.js] Passport.js와 로그인</a>
<a href="https://data-jj.tistory.com/53">REST-API 활용한 카카오 소셜 로그인 구현(feat. React)</a>
<a href="https://blogofpjj.tistory.com/47">Node.js - 카카오 로그인 구현하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | [실전프로젝트] 마이페이지 좋아요, 스크랩, 작성 게시글 모아보기]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EC%9D%B4%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A2%8B%EC%95%84%EC%9A%94-%EC%8A%A4%ED%81%AC%EB%9E%A9-%EC%9E%91%EC%84%B1-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EB%AA%A8%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EC%9D%B4%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A2%8B%EC%95%84%EC%9A%94-%EC%8A%A4%ED%81%AC%EB%9E%A9-%EC%9E%91%EC%84%B1-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EB%AA%A8%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 15 May 2023 10:19:17 GMT</pubDate>
            <description><![CDATA[<h2 id="구현한-기능">구현한 기능</h2>
<p><img src="https://velog.velcdn.com/images/docu_a/post/de7f21f8-f514-4e57-ab17-177d777f5be5/image.png" alt="마이페이지 피그마"></p>
<ul>
<li><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/mypage.repository.js#L146">내가 좋아요한 전시 조회</a> (최근 좋아요 순)</li>
<li><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/mypage.repository.js#L146">내가 스크랩한 전시 조회</a> (최근 스크랩 순)</li>
<li><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/mypage.repository.js#L83">내가 작성한 전시 조회</a> (최근 작성 순)</li>
<li><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/mypage.repository.js#L296">내가 좋아요한 아트그램 조회</a> (최근 좋아요 순)</li>
<li><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/mypage.repository.js#L296">내가 스크랩한 아트그램  조회</a> (최근 스크랩 순)</li>
<li><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/repositories/mypage.repository.js#L222">내가 작성한 전시 아트그램 조회</a> (최근 작성 순)</li>
</ul>
<h2 id="구현-방법">구현 방법</h2>
<ul>
<li><p><strong>작성한</strong> 전시/아트그램 게시글 기능 구현 시 고려한 점:
soft delete 방식으로 삭제되면 &quot;ES04&quot;, &quot;AS04&quot;로 status code만 바꿔주도록 처리를 하였기 때문에 04가 아닌 게시글을, 최근 작성순으로 조회하였다</p>
</li>
<li><p><strong>좋아요/스크랩</strong> 전시/아트그램 게시글 기능 구현 시 고려한 점:
좋아요한 전시 게시글을 예시로 들자면, </p>
<pre><code class="language-js">services/mypage.service.js
/**
 * 내가 좋아요한 전시회 조회
 * @param {number} limit
 * @param {number} offset
 * @param {string} userEmail
 * @returns 내가 좋아요한 전시회
 */
getMyLikedExhibition = async (limit, offset, userEmail) =&gt; {
  const myLikes = await this.mypageRepository.findAllMyLikedExhibitionId(
    userEmail
  ); 

  if (!myLikes.length) {
    return { message: &quot;좋아요한 게시글이 없습니다.&quot; };
  }

  const myLikedExhibitionIds = myLikes.map((elem) =&gt; elem.exhibition_id);
  const getMyLikedExhibitions = await this.mypageRepository.findMyExhibition(
    limit,
    offset,
    myLikedExhibitionIds
  );

  return getMyLikedExhibitions;
};</code></pre>
</li>
</ul>
<p><code>findAllMyLikedExhibitionId</code> : 
내가 좋아요한 전시게시글 id를 좋아요 누른순으로 조회</p>
<p><code>findMyExhibition</code> : 
조회한 id array를 기반으로 
(1) Op.in으로 좋아요한 전시회만,
(2) Op.ne로 04 status code가 아닌 게시글만,
(3) 페이지네이션 정보를 포함하여 조회
=&gt; 이후 스크랩한 게시글 조회 시에도 메서드를 재활용할 수 있었다</p>
<h2 id="도전">도전</h2>
<h3 id="--시퀄라이즈-에러와의-사투-난이도-상">- 시퀄라이즈 에러와의 사투 (난이도 상)</h3>
<p>limit, offset을 추가한 뒤로 원래 잘되던 코드에서 에러가 나기 시작했다. 신기한건 limit, offset 부분에서 에러가 나는게 아니라 원래 잘되던 다른 조회 부분에서 에러가 났었다는 점이었다. 사실 정확한 원인이 어디서부터인지 몰라서 이것저것 수정할때마다 에러메시지가 바껴서 더 혼란스러웠던 것 같다. 그때 메시지들을 바로 기록해뒀어야하는데 기능 구현에 마음이 급해서 아카이빙을 못한게 아쉽다. gpt도 고장이 나서 <img src="https://velog.velcdn.com/images/docu_a/post/48bb34df-3517-4163-b5e8-9436dbb54e1f/image.png" alt=""> 수정을 했다고 답했는데 아무리봐도 수정된데가 없어서 어디가 수정됐냐고 물어보니 수정된게 없다는 답변을 했다. gpt의 반박시 님말맞음에 뒷통수를 맞았다. gpt에 너무 의존하면 안되겠다는 경각심은 늘 갖고 있었지만 직접 경험해보고 더 뼈저리게 느꼈던 것 같다. 검색을 하다보니 한 오분만에 답을 찾았다. <a href="https://velog.io/@flobeeee/sequelize-NM-limit-offset-%EA%B0%80-%EA%B0%80%EB%8A%A5%ED%95%98%EB%8B%A4">flobeeee.log</a>님의 게시글을 보고 속는셈치고 했는데 subQuery: false로 문제가 해결되었다. 원리가 궁금했는데 시퀄라이즈 공식문서에는 없는 내용<a href="https://stackoverflow.com/a/42445474">이라고 한다</a>. 어떻게 이런 방법을 찾으신거징. 어쩌다 해결이라 약간 찝찝한 느낌이었다.</p>
<h3 id="--hasbackpage-난이도-하">- hasBackPage (난이도 하)</h3>
<p><img src="https://velog.velcdn.com/images/docu_a/post/8af5d044-03f3-47ab-ad17-1df29aaec6bd/image.png" alt=""> 이전, 다음 페이지가 있는지 여부에 따라 버튼이 색상이 달라지는 포인트가 있었는데 이 부분도 데이터를 줌으로써 해결할 수 있다는 것을 배웠다. 전시쪽에서 무한스크롤을 구현하셨을때 hasNextPage 데이터가 필요하셨다고해서 팀장님이 hasNextPage 구현한 부분을 참고하라고 하셔서 참고했는데 버튼에서는 이렇게 활용될줄 몰랐다. 프론트 분이 hasNextPage처럼 hasBackPage가 있으면 좋을 것 같다 하셔서 offset이 0초과면 true를 반환하게해서 구현했다. <img src="https://velog.velcdn.com/images/docu_a/post/c53e4f75-cbcf-4b7b-b786-19f3c5cde52d/image.png" alt=""></p>
<h2 id="배운-점">배운 점</h2>
<ul>
<li>백엔드 팀장님이 전시페이지 앞서 무한스크롤 기능 구현하시며 알려주신 sequelize limit, offset 기능</li>
</ul>
<h2 id="완성본">완성본</h2>
<p><img src="https://velog.velcdn.com/images/docu_a/post/10ce41cd-0245-4913-8f5f-74d032cc5d6a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | [실전프로젝트] Redis, jsonwebtoken, nodemailer를 사용한 이메일 인증 기반 회원가입]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Redis-jsonwebtoken-nodemailer%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EB%B0%98-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%8B%A4%EC%A0%84%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Redis-jsonwebtoken-nodemailer%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EB%B0%98-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</guid>
            <pubDate>Mon, 15 May 2023 10:13:13 GMT</pubDate>
            <description><![CDATA[<h2 id="구현한-기능">구현한 기능</h2>
<p>이메일 인증 기반 회원가입 기능</p>
<h2 id="구현-방법">구현 방법</h2>
<ul>
<li><p><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/services/user.service.js#L70">이메일 중복확인 API</a></p>
</li>
<li><p><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/services/user.service.js#L83">이메일 인증번호 발송 API</a>
이메일 중복확인 API를 거친 이메일에 한해,
(1) Math.random()으로 6자리 랜덤 인증번호 생성
(2) jsonwebtoken으로 토큰을 생성하고 만료시간을 3분으로 설정
(3) redis에 토큰값을 set하고 expire시간을 4분으로 설정
(4) config폴더에서 nodemailer로 transport 객체를 생성하고 user service단에서 이를 import하여 메일 content 작성 후 발송</p>
</li>
<li><p><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/services/user.service.js#L174">이메일 인증번호 검증 API</a>
(1) client에서 email과 code를 받아옴
(2) redis에서 set해둔 이메일토큰값을 get
(2-1) key값이 없다면 인증시간이 만료됐다는 에러메시지
(2-2) 있다면 jwt verify를 한 후 client에서 입력된 인증번호 값과 Redis에 저장됐던 값 대조
(2-2-1) 일치하지 않으면 일치하지 않는다는 에러메시지
(2-2-2) 일치한다면 이메일 인증에 성공하였다는 메시지</p>
</li>
<li><p><a href="https://github.com/Muse-O/Backend/blob/fdc7ab871fc73bf50db00b264483ffe35f9f934e/services/user.service.js#L198">회원가입 API</a></p>
</li>
</ul>
<h2 id="배운점">배운점</h2>
<ul>
<li>nodemailer 라이브러리 사용법</li>
<li>기본적인 redis 커맨드 사용법</li>
<li>만료 기간이 있는 일회성 데이터에 redis를 사용하면 좋겠다</li>
</ul>
<h2 id="아쉬운점">아쉬운점</h2>
<ul>
<li>jsonwebtoken만으로는 보안성이 떨어질지 않을까 의문, 토큰키를 탈취당하거나, 토큰키를 갖고있는 사람이라면 인증번호 조회가 가능하다는점 =&gt; bycrypt로 해싱하는 과정을 추가하면 더 좋을 것같다</li>
<li>이메일이 고유값이여서 닉네임은 자유롭게 설정할 수 있게 했는데 생각해보니 중복된 닉네임으로 이상한 활동을 하는 사람이 있을 수 있다는 점을 고려하지 못한 것 같다. 유저피드백에서 이에 대해 지적해주신 분이 있어서 고민해볼 수 있었고 프론트 분께 반영해볼 수 있냐 제안했는데 아쉽게도 더이상 기능 구현하실 의사가 없으시다고 하셔서 적용하지 못했다. 그래도 왜 대부분의 커뮤니티에서 닉네임 또한 고유값이었는지 짚고 넘어갈 수 있어서 좋은 경험이었다.<img src="https://velog.velcdn.com/images/docu_a/post/b0a2bb4d-7113-413e-9a16-e1b116533f6f/image.png" alt=""></li>
</ul>
<h2 id="완성본">완성본</h2>
<p>완성본과... 보기만해도 기분좋았던, 힘이나는 유저피드백</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/docu_a/post/c6daa516-dee1-4480-9865-f1f435db19d8/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/c889cd71-5f7a-42e8-b366-718edb6530e4/image.png" alt=""></th>
</tr>
</thead>
</table>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/docu_a/post/3e71aeca-efe8-49a6-8f14-b5524bc8a1ab/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/13b34571-3bf9-40f6-8774-4a8edb1b9111/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/docu_a/post/2edb8332-c65a-4bf1-a7cc-4a0f5f1bb0db/image.png" alt=""></th>
</tr>
</thead>
</table>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://velog.io/@seunghwa17/%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D%EB%B2%88%ED%98%B8Nodemailer-Redis">회원가입 이메일 인증번호 Nodemailer + Redis</a>
<a href="https://ant-programmer.tistory.com/70">Nodemailer로 사용자에게 메일보내기</a>
<a href="https://velog.io/@neity16/NodeJs-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84nodemailer">NodeJs - 이메일 인증 (nodemailer)</a>
<a href="https://choice91.tistory.com/62">[Node.js] Nodemailer로 이메일 전송하기 with Gmail</a>
<a href="https://youtu.be/jgpVdJB2sKQ">Redis Crash Course</a>
<a href="https://inpa.tistory.com/entry/REDIS-NODE-%F0%9F%93%9A-%EB%85%B8%EB%93%9Cexpress%EC%97%90%EC%84%9C-redis-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%BA%90%EC%8B%B1-%EC%84%B8%EC%85%98-%EC%8A%A4%ED%86%A0%EC%96%B4">[REDIS] 📚 Node.js 에서 redis 모듈 사용법 (캐싱 &amp; 세션 스토어)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Why? Nginx]]></title>
            <link>https://velog.io/@docu_a/Why-Nginx</link>
            <guid>https://velog.io/@docu_a/Why-Nginx</guid>
            <pubDate>Sat, 08 Apr 2023 09:08:46 GMT</pubDate>
            <description><![CDATA[<p>우리는 node.js로 이미 웹서버를 개발했는데 왜 또하나 더 병행해서 써야 하나?</p>
<p>딱 이게 궁금했는데 이 영상에서 설명해주셨다!
<a href="https://youtu.be/ZJpT-Wa-pZ8">[Nginx] (1/2) 도대체 뭐길래 카카오, 네이버에서 사용할까</a></p>
<h2 id="what-무엇인가">WHAT? 무엇인가?</h2>
<h3 id="1-flow">1) flow</h3>
<p>프론트 &lt;=&gt; 웹서버 &lt;=&gt; WAS &lt;=&gt; 데이터베이스
브라우저 &lt;=&gt; Nginx &lt;=&gt; Node.js &lt;=&gt; MySQL</p>
<h3 id="2-web-server와-wasweb-application-server의-차이">2) Web Server와 WAS(Web Application Server)의 차이</h3>
<p><code>Web Server</code>
단순히 정적 파일을 응답 (이미지, html, css, 단순 js 파일)</p>
<p><code>WAS (Web Application Server)</code> - 현업에서는 웹서버라고도 불림
클라이언트 요청에 대해 동적인 처리가 이루어진 후 응답
동적인건 뭐야? 로그인할래 아이디패스워드 줄테니까 로그인시켜줘</p>
<p>Node.js는 웹서버로도 사용할 수 있고 was로도 사용할 수 있다</p>
<h3 id="3웹-서버-왜써야하는가">3)웹 서버 왜써야하는가?</h3>
<ul>
<li>웹 서버 별도로 운영하는 이유는 WAS의 부담을 줄여주기 위해
정적인 이미지같은거 주는거까지 WAS가 부담하기에는 일이 너무 많음. 
nginx같은 웹서버에 &quot;나 일 너무 많으니까 너가 대신해줘!&quot;</li>
</ul>
<h2 id="why-왜써야하는가">WHY? 왜써야하는가?</h2>
<h3 id="4-굳이-nginx를-사용하는-이유">4) 굳이 Nginx를 사용하는 이유</h3>
<h4 id="1-빠르다">1. 빠르다</h4>
<p>3000개의 동시 요청을 했을때와 50개의 동시 요청을 했을때 메모리 사용량이 거의 비슷함
초당 요청 처리: 동접자가 많아질 수록 다른 것에 비해 처리할 수 있는게 많다. 굉장히 빠르다</p>
<h4 id="2-리버스-프록시로-사용-가능">2. 리버스 프록시로 사용 가능</h4>
<p>프록시: 대리</p>
<ul>
<li>리버스 프록시: 인터넷과 백엔드 사이의 서버</li>
<li>포워드 프록시: 클라이언트와 인터넷 사이에 있는</li>
</ul>
<p>리버스 프록시를 사용하면 왜 좋나?
<img src="https://velog.velcdn.com/images/docu_a/post/422463d5-6033-4386-9f6d-969cd1985593/image.png" alt=""></p>
<p>2-1 <code>로드 밸런싱</code>: 사용하는 앱서버가 여러대일 때 어떤 요청이오면 클라이언트1너는 얘, 2번너는 얘 이런식으로 길을 정해줌
2-2 <code>캐싱 서버</code> 역할: 이미지를 요청했을때 너 뒷단까지 갈 필요 없어 내가줄게~
2-3 <code>보안</code>: WAS가 응답할때 그 응답에는 굉장히 많은 데이터들이 들어있음. 외부에서는 어디서 이 데이터가 온건지 알 수 없게끔, 암호화 해주는 기능을 리버스프록시가 할 수 있다.
2-4 <code>SSL을 지원</code>: 우리사이트는 보안이 잘돼있다. 인증해. nginx가 https 인증서를 제공해 줄 수 있다.
https도 nginx를 쓰면 쉽게 설정할 수 있구나~
2-5 <code>웹페이지 접근을 인증</code>해준다: 로그인 정보가 올바른 정보인지 WAS에서 검증할 수 있는데 굳이 WAS에서 하지 않고 Nginx에서 해줄 수 있다. 너는 관리자야~ 너는 관리자 아니고 사용자야~
2-6 <code>압축</code>: 클라이언트가 보내는 내용이 텍스트타입을 압축해서 전달해줄 수 있다
2-7 <code>이벤트루프 방식</code> 사용: node.js도 이벤트 루프라는 녀석이 비동기로 동작하도록 해줌, 비동기 방식으로 동작을 해서 상당히 많은 트래픽을 동시에 처리할 수 있다. 10000개의 트래픽까지
등등의 기능
<img src="https://velog.velcdn.com/images/docu_a/post/d0ce6029-b152-4a52-90d9-1b4f4ed6691e/image.png" alt=""></p>
<p>nginx도 비동기, Node.js도 비동기 =&gt; 둘은 환상의 조합</p>
<blockquote>
<p>Nginx를 Proxy 서버로 앞단에 두고 Node.js를 뒷단에 두면, 버퍼 오버플로우 취약점에 의한 공격을 방지할 수 있다 -Node.js 창시자, 라이언 달-</p>
</blockquote>
<p>버퍼 오버플로우? 버퍼(메모리: 스택과 힙이 메모리 안의 구조로 있다)는 할당돼있는 한계점이 있는데 그 한계를 넘어가면 오버플로우 된다</p>
<h2 id="how-어떻게-써야하는가">HOW? 어떻게 써야하는가?</h2>
<p>완전 이해가 쏙쏙가는 그림
http로 통신하면 엔진엑스의 80번 포트에서 443포트로 redirect 시켜줌.
443에서는 노드의 5000으로 갈 수 있게 해줌
http로 접속해도 https로 접속하게 해주는 효과
<img src="https://velog.velcdn.com/images/docu_a/post/eef2dfd2-2aef-4d89-b209-ddbfb4dcabc4/image.png" alt=""></p>
<pre><code class="language-shell">sudo apt-get update
sudo apt-get upgrade -y #OS install

sudo apt-get install nginx

sudo service nginx start

cd/etc/ngins/sites-enabled
sudo rm default
sudo vi default
# 설정파일 안에 들어가서 설정 지우고 내가 만든 설정 추가

server{
    listen 80;
    server_name idu-market.shop;

    # access_log /var/log/nginx/reverse-access.log;
    # error_log /var/log/nginx/reverse-error.log;

    if ($host = idu-market.shop) {
        return 301 https://$host$request_uri:
    }
}

server {
    listen 443 ssl: #인증서 설정을 해주겠다
    server_name idu-market.shop:

    # access_log /var/log/nginx/reverse-access.log;
    # error_log /var/log/nginx/reverse-error.log;

    location / {
        proxy_pass http://127.0.0.1:5000; #5000번포트로 우회시켜주겠다
    }

sudo service nginx restart</code></pre>
<p>생각1 : SSL 적용, cloudflare로 할 것인가 let&#39;s Encrypt로 할것인가.
let&#39;s encrypt
장점1) 무료, 인증서 갱신 자동</p>
<p>생각2: 어떤 사용자인지 접근 권한 인증해주는건 우리 페이지에 적용해볼만한 기능인 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 배포]]></title>
            <link>https://velog.io/@docu_a/AWS-%EB%B0%B0%ED%8F%AC-3k6mvacq</link>
            <guid>https://velog.io/@docu_a/AWS-%EB%B0%B0%ED%8F%AC-3k6mvacq</guid>
            <pubDate>Sat, 08 Apr 2023 00:55:00 GMT</pubDate>
            <description><![CDATA[<h3 id="1-인스턴스-시작시">1) 인스턴스 시작시</h3>
<ul>
<li>ubuntu server 18.04 LTS</li>
<li>인스턴스 타입 t2.micro(프리티어사용가능)</li>
<li>새 키페어 생성 : 키페어 이름 입력, 잘 보관</li>
</ul>
<h4 id="-인바운드-규칙편집">+) 인바운드 규칙편집</h4>
<p>http anywhere IPv4 80
22는 기본</p>
<h4 id="2-터미널에서-ec2-접속하기">2) 터미널에서 EC2 접속하기</h4>
<pre><code class="language-shell">sudo chmod 400 받은키페어를끌어다놓기

ssh -i 받은키페어를끌어다놓기 ubuntu@AWS에적힌내아이피

sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000
80-&gt;3000 전달</code></pre>
<h4 id="참고-리눅스-명령어">참고) 리눅스 명령어</h4>
<pre><code class="language-shell">ls: 내 위치의 모든 파일을 보여준다.

pwd: 내 위치(폴더의 경로)를 알려준다.

mkdir: 내 위치 아래에 폴더를 하나 만든다.

cd [갈 곳]: 나를 [갈 곳] 폴더로 이동시킨다.

cd .. : 나를 상위 폴더로 이동시킨다.

cp -r [복사할 것] [붙여넣기 할 것]: 복사 붙여넣기

rm -rf [지울 것]: 지우기

sudo [실행 할 명령어]: 명령어를 관리자 권한으로 실행한다.
sudo su: 관리자 권한이 있는 계정으로 접속한다. (exit 입력하면 관리자 계정에서 로그아웃)</code></pre>
<p>3) Node설치</p>
<pre><code class="language-shell">curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -

sudo apt-get install -y nodejs


node -v
npm -v</code></pre>
<p>참고) MongoDB 설치</p>
<pre><code class="language-shell">wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -

echo &quot;deb [ arh=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse&quot; | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list

sudo apt-get update

sudo apt-get install -y mongodb-org

sudo service mongod start
// 아무 반응 없을 시 잘 실행됨</code></pre>
<p>4) EC2 인스턴스에서 서버 프로그램 실행 및 웹사이트 접속</p>
<pre><code class="language-shell">git clone 깃헙 https클론주소

cd 프로젝트파일
npm install
node app.js</code></pre>
<p>5) pm2 설치</p>
<pre><code class="language-shell">sudo -s # 관리자 계정으로 전환
npm install -g pm2 # 전역 프로그램으로 설치하겠다
pm2 start app.js</code></pre>
<pre><code class="language-shell"># pm2 명령어
# 재시작
pm2 restart app.js. # app.js를 재시작합니다.
# 0번 ID의 서비스 (app)을 재실행합니다.
pm2 restart 0

# 관리중 서비스 목록 출력
pm2 list 

# 0번 ID의 서비스(app) 을 하는 명령어입니다. 
pm2 delete 0

# 실행한 서비스들의 로그 출력
pm2 log

# 마지막 발생한 로그 순서대로 100개 출력
pm2 logs --lines 100</code></pre>
<p>참고: <a href="https://pm2.keymetrics.io/docs/usage/process-management/">PM2 CLI 명령어 모음</a></p>
<p>6) 도메인 연결
가비아 마이페이지
-&gt; DNS 관리
-&gt; 도메인 연결
-&gt; DNS 설정 클릭
-&gt; 호스트 이름에 <code>@</code> IP주소에 EC2 instance의 IP 입력</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git]]></title>
            <link>https://velog.io/@docu_a/Git</link>
            <guid>https://velog.io/@docu_a/Git</guid>
            <pubDate>Mon, 27 Mar 2023 09:41:49 GMT</pubDate>
            <description><![CDATA[<h4 id="git">Git</h4>
<ul>
<li>체계적 개발과 프로그램 배포를 도와주는 형상 관리 도구 또는 버전 관리 시스템</li>
</ul>
<h4 id="git-명령어">Git 명령어</h4>
<p><code>git init</code>
<code>git add</code>
<code>git commit</code>
<code>git remote add</code>
<code>git push</code>
<code>git clone</code>
<code>git pull</code></p>
<h4 id="배포-시-git-활용">배포 시 Git 활용</h4>
<blockquote>
<p>처음 배포시
<code>git clone</code>
<code>npm install</code>
서버 켜기</p>
</blockquote>
<blockquote>
<p>이미 배포했던 서버의 코드 최신으로 재시작
<code>git pull</code>
(필요시)<code>npm install</code>
서버 재시작</p>
</blockquote>
<h4 id="local-repo에서-remote-repo로-올리기">Local repo에서 Remote repo로 올리기</h4>
<p><strong>Local Repository</strong>에 <strong>Remote Repository</strong>를 연결하고, <strong>Local Repository</strong>에 있던 <strong>Commit History</strong>를 <strong>Remote Repository</strong>에 업로드
<strong>Remote Repository</strong>에 있는 내용은 언제 어디서든 컴퓨터에 받아서(<strong>clone</strong>) 작업이 가능</p>
<pre><code class="language-shell">git init
git remote add origin &lt;github 페이지에 나오는 ssh 주소&gt;
git remote -v //현재 프로젝트의 remote 저장소 확인 가능
git add .
git commit -m &quot;initial commit&quot;
git push -u origin master (또는 main) 
// 원격 repo에 새로운 브랜치를 생성하기 위해 -u옵션을 열고,
// 원격 repo 이름으로 등록해둔 origin을 적어 어떤 원격 repo에 push 할것인지 작성

// 다음 변동 사항시
git add .
git commit -m
git push
// Remote Repository에도 존재하는 브랜치에서 작업하는 경우,
// 단순히 git push 명령어로도 Local Repository에 있는 Commit을 올릴 수 있음</code></pre>
<p>협업하면서 배운 깃</p>
<pre><code class="language-shell">팀원들 변경사항 반영하기 전
내 기능별로 커밋 해두기
git checkout dev
git pull origin dev
git checkout 내브랜치
git pull origin dev
--- 충돌 해결 ---
git add .
git commit -m “팀원분들 변경사항 반영”
git push origin 내브랜치
깃허브에서 머지</code></pre>
<pre><code class="language-shell">stash 잘쓰신 이전 조원분이 공유해주신 방법
본인이 작업하다 변경사항이 있는데 메인에 머지가 됐네, 풀하러 가야겠다 하고 체크아웃 해버리면 안돼.
변경사항이 있는 상태에서 체크아웃 해버리면 같이 물고가버려.
그렇기때문에 임시저장이라는 기능 쓸 수도 있어
git stash
이거 치면 지금 변경 했던 거 임시저장. 변경된게 사라진다. 그러면 다시
git checkout main
git pull origin main
git checkout “본인브랜치”
git pull origin main (본인 브랜치도 업데이트해줘서 그래프 동일선상으로 맞춰주고)
git stash pop (임시 저장 했던 거 다시 불러오는 거) (pop이 가장 최신것을 불러오고 저장했던 내역을 삭제한다는 말.)</code></pre>
<p><code>git branch -vv</code>
<code>git config --list</code>
<code>rm -rf .git</code> 더이상 깃 프로젝트 아님
<code>git rm --cached *</code> git add 취소</p>
<p><a href="https://jihyuns-today.tistory.com/entry/Git-%EC%8B%A4%EC%88%98%EB%A1%9C-%EC%98%AC%EB%A6%B0-env-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0-commit-history%EA%B9%8C%EC%A7%80-%EC%99%84%EC%A0%84-%EC%82%AD%EC%A0%9C">실수로 올린 파일 삭제하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Express.js, Router, Module]]></title>
            <link>https://velog.io/@docu_a/Express.js-Router-Module</link>
            <guid>https://velog.io/@docu_a/Express.js-Router-Module</guid>
            <pubDate>Mon, 27 Mar 2023 07:00:10 GMT</pubDate>
            <description><![CDATA[<p><code>npm init</code>
&quot;package.json&quot; file 만들어짐
<code>npm install 모듈명1 모듈명2</code>
모듈설치, package.json에 설정된 모듈과 해당 모듈이 참고하고 있는 다른 모듈도 &quot;node_modules폴더&quot;에 설치됨. 배포시 포함하면 안되는 폴더
<code>express.js</code>
웹서버 자체가 아닌 Node.js로 서버를 빠르고 간편하게 만들 수 있게 도와주는 웹 프레임워크</p>
<p>？ 이미 사용중인 포트를 찾아서 종료하고 싶다면?</p>
<ol>
<li>특정 포트 찾기<pre><code class="language-shell">lsof -i :원하는 포트</code></pre>
</li>
<li>점유하고있는 PID 찾아서 종료시킴<pre><code class="language-shell">kill -9 포트를 점유하고 있는 PID</code></pre>
<h4 id="routing">Routing</h4>
<pre><code class="language-js">Routing은 클라이언트의 요청 조건(메서드, 주소 등)에 대응해 응답하는 방식
Router: 클라이언트의 요청을 쉽게 처리할 수 있게 도와주는 Express.js 기본 기능 중 하나
</code></pre>
</li>
</ol>
<p>// routes/goods.js
const express = require(&#39;express&#39;);
const router = express.Router(); // 익스프레스 Router 함수 사용</p>
<p>// localhost:3000/api/ GET
router.get(&quot;/&quot;, (req, res) =&gt; {
  res.send(&quot;default url for goods.js GET Method&quot;);
});</p>
<p>// localhost:3000/api/about GET
router.get(&quot;/about&quot;, (req, res) =&gt; {
  res.send(&quot;goods.js about PATH&quot;);
});</p>
<p>module.exports = router; // app.js에서 사용하기 위해 내보내 주는 코드 추가</p>
<pre><code>```js
// routes/index.js로 연결
const express = require(&#39;express&#39;);
const app = express();
const globalRouter = require(&#39;./routes&#39;);
const port = 4000;

app.use(express.json()); // req.body 바디파싱
app.use(&#39;/&#39;, globalRouter);

app.listen(port, () =&gt; {
    console.log(`listening at http://localhost:${port}`);
});</code></pre><h4 id="module의-이해">Module의 이해</h4>
<p><code>Module</code>
javascript 파일 단위로 분리된 코드
보통 한개의 파일이 한개의 모듈이 됨</p>
<pre><code class="language-js">// Module export하는 다양한 방법

(1) 화살표 함수
// 모듈을 호출했을 때, add 키 값에는 add 변수 함수가 가지고 있는 값이 할당된다.
const add = (a, b) =&gt; {
  return a + b;
}
exports.add = add;

(2) 익명 함수
// 모듈을 호출했을 때, add 키 값에는 (a,b){return a + b;} 익명 함수가 할당되는 방법
exports.add = function (a, b) {
  return a + b;
}

(3) 함수 선언문
// 모듈을 호출했을 때, add 키 값에는 add 함수가 들어가는 방법이다.
function add(a, b) {
  return a + b;
}
module.exports = { add: add };

(4) 객체
// 모듈 그 자체를 add 함수로 할당
function add(a, b) {
  return a + b;
}
module.exports = add;</code></pre>
<h4 id="req-res">req, res</h4>
<p>Request: 클라이언트가 서버에게 전달하려는 정보나 메시지 담는 객체
Response: 서버에서 클라이언트로 응답 메시지 전송시켜주는 객체</p>
<p>Node.js 서버 모듈: 대표적으로 http 모듈과 Express 모듈 존재 (Express모듈이 http 모듈의 메서드도 사용할 수 있지만 추가 제공하는 메서드나 속성들을 사용할 수 있음)</p>
<p>Express 모듈의 req, res 객체</p>
<blockquote>
<p><code>req.app</code>
req 객체 통해 app 객체에 접근 가능
<code>req.ip</code>
요청한 클라이언트의 ip 주소
⭐️<code>req.body</code>
request 호출시 body로 전달된 정보 담김
express.json() Middleware를 이용해야 사용가능
데이터를 생성하거나 수정이 필요한 데이터의 전달을 위해 사용
POST, PUT과 같은 HTTP 메서드에서 사용
⭐️<code>req.params</code>
라우터 매개 변수에 대한 정보가 담김
⭐️<code>req.query</code>
쿼리 스트링으로 전달된 정보가 담긴 객체
클라이언트가 요청(Request)을 보냈을 때, URL에 원하는 Key-Value를 삽입하여 데이터를 전달합니다.
URL의 마지막에 <code>?</code>기호를 이용해 Query String을 사용할 수 있습니다.
ex) <code>https://youtube.com?name=뽀짝이&amp;age=27</code>
특정 콘텐츠의 위치를 표시하거나 웹 페이지에 특정한 옵션을 설정할 때 사용합니다.
ex) 게시글의 정렬, 특정 날짜의 게시글만 출력하는 옵션 설정 등
GET과 같은 Http Method에서 사용됩니다.
<code>req.cookies</code>
request 호출 시 cookie정보가 담긴 객체
cookie-parser Middleware를 이용해야 해당 객체 사용가능
<code>req.get(Header)</code>
헤더에 저장된 값 가져오고 싶을 때 사용</p>
</blockquote>
<blockquote>
<p><code>res.app</code>
res 객체를 통해 app 객체에 접근 가능
⭐️<code>res.status(코드)</code>
response에 HTTP 상태 코드 지정
⭐️<code>res.send(데이터)</code>
데이터를 포함하여 response 전달
⭐️<code>res.json(JSON)</code>
JSON형식으로 Response 전달
<code>res.end()</code>
데이터 없이 Response 전달
<code>res.direct(주소)</code>
리다이렉트할 주소와 함께 Response전달
<code>res.cookie(Key, Value, Option)</code>
쿠키 설정 시 사용
<code>res.clearCokkie(Key, Value, Option)</code>
쿠키 제거 시 사용</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP]]></title>
            <link>https://velog.io/@docu_a/HTTP</link>
            <guid>https://velog.io/@docu_a/HTTP</guid>
            <pubDate>Mon, 27 Mar 2023 03:21:53 GMT</pubDate>
            <description><![CDATA[<h3 id="http">HTTP</h3>
<ul>
<li>데이터를 주고 받는 양식을 정의한 &quot;통신 규약&quot;중 하나
(통신 규약: Protocol)</li>
</ul>
<h3 id="network-창-살펴보기">NETWORK 창 살펴보기</h3>
<h4 id="headers">Headers</h4>
<ol>
<li>General: 브라우저에서 서버로 보낸 Request 데이터</li>
<li>Request Headers: 브라우저에서 서버로 보낸 Request 데이터</li>
<li>Response Headers: 서버가 웹 페이지 데이터와 함께보낸 추가 데이터</li>
</ol>
<h4 id="response">Response</h4>
<p>이것은 서버에서 여러분의 브라우저로 반환해준 웹 페이지를 그려주기 위한 데이터</p>
<h3 id="http-구성요소">HTTP 구성요소</h3>
<h4 id="1-method">1. Method</h4>
<p>GET, POST</p>
<h4 id="2-header-추가-데이터-메타-데이터">2. Header (추가 데이터, 메타 데이터)</h4>
<ul>
<li>브라우저가 어떤 페이지를 원하는지</li>
<li>요청 받은 페이지를 찾았는지</li>
<li>성공적으로 찾았는지<h4 id="3-payload-데이터-실질적인-데이터">3. Payload (데이터, 실질적인 데이터)</h4>
</li>
<li>서버가 응답을 보낼 때에는 항상 <strong>Payload</strong>를 보낼 수 있다</li>
<li>클라이언트(브라우저)가 요청을 할 때에도 <strong>Payload</strong>를 보낼 수 있다</li>
<li>&quot;<code>GET</code> method를 제외하곤 <strong>모두 Payload를 보낼 수 있다</strong>&quot;* 는게 HTTP에서의 약속</li>
<li>추가적으로 DELETE method에서 Payload를 보낼수있지만, 보통 많은 경우에 Payload를 보내지않고있다</li>
</ul>
<p>⬇️<a href="https://brunch.co.kr/@swimjiy/2">[해치지 않는 웹] 1. 웹 동작 방식</a>
 <strong>웹 클라이언트</strong> =request=&gt; <strong>웹서버</strong> =WAS=&gt; <strong>DB</strong> =WAS=&gt; <strong>웹서버</strong> =response=&gt; <strong>웹클라이언트</strong></p>
<ul>
<li><strong>웹클라이언트</strong> : 사용자가 웹에 접근하는 프로그램 <ul>
<li>크롬 등의 웹브라우저를 웹클라이언트라고함</li>
</ul>
</li>
<li><strong>웹서버</strong>: 웹페이지, 사이트 또는 앱을 저장하는 프로그램 <ul>
<li>클라이언트에서 url 주소의 메인페이지를 보내달라 요청하면, 서버는 확인 후 페이지를 만드는데 필요한 html,css,js 등을 클라이언트에게 보내줌</li>
<li>아파치 웹서버, GWS, IIS 등</li>
</ul>
</li>
<li>** WAS** : 웹 어플리케이션 서버 <ul>
<li>사용자 컴퓨터나 장치에 웹 어플리케이션을 수행해주는 미들웨어</li>
<li>아파치 톰캣, 레진, 제이런 등</li>
<li>주문을 확인하고 역할을 분배하는게 웹서버, 실제로 요리하는 요리사가 WAS<ul>
<li>로직 수행하다가 DB접근이 필요하면 SQL 질의를 통해 데이터 요청하고 DB가 응답을 보냄</li>
</ul>
</li>
</ul>
</li>
<li><strong>DB</strong> : 데이터 정보를 저장</li>
</ul>
<p>⬇️<a href="https://brunch.co.kr/@swimjiy/3">[해치지 않는 웹] 2. http 메시지</a>
 클라이언트가 서버에 요청하는 메시지가 Request Message,
 클라이언트의 요청을 해석한 서버가 응답하는 메시지가 응답 메시지 Response Message
 <strong>요청 메시지(Request Message)</strong></p>
<ol>
<li>요청 라인 (Request Line)</li>
</ol>
<ul>
<li>메서드(어떻게) : 클라이언트가 무엇을, 어떻게 처리하고자 한다 (GET, POST, PUT, DELETE)</li>
<li>경로(무엇을) : 메서드를 참고하여 수행할 대상, 가져오려는 리소스의 경로</li>
<li>프로토콜 버전 : HTTP 프로토콜의 버전. 1.1버전과 보안성을 강화한 2버전이 있음</li>
</ul>
<ol start="2">
<li>요청 헤더 (Request Headers)</li>
</ol>
<ul>
<li>서버에 대한 추가적인 정보</li>
<li>호스트의 정보, 접속 중 사용자의 정보, 열려고하는 페이지의 정보</li>
</ul>
<ol start="3">
<li>공백 라인 (Empty Line)</li>
</ol>
<ul>
<li>헤더와 본문 구분</li>
</ul>
<ol start="4">
<li>요청 메시지 본문 (Request Message Body)</li>
</ol>
<ul>
<li><p>필수 요소 아님</p>
</li>
<li><p>POST처럼 새로운 자원을 추가하는 경우 본문에 작성하여 전달</p>
</li>
<li><p><em>응답 메시지(Request Message)*</em>
클라이언트 요청에 대한 서버의 답변</p>
</li>
</ul>
<ol>
<li>응답 라인 (Request Line)</li>
</ol>
<ul>
<li>프로토콜 버전</li>
<li>상태코드
  100번대(조건부 응답): 요청 받았으며 작업 계속
 200번대(성공): 정상적으로 요청 수행했을때
 300번대(리다이렉션 완료): 클라이언트가 요청을 마치기 위해 추가동작을 취해야할 때
 400번대(요청 오류): 클라이언트의 요청에 오류가 있을때
 500번대(서버 오류): 서버가 들어온 요청을 수행하지 못했을때</li>
</ul>
<ol start="2">
<li>응답 헤더 (Request Headers)</li>
</ol>
<ul>
<li>가져온 리소스</li>
<li>HTML이라면 태그 형태의 코드, 이미디나 동영상이면 그에 맞는 코드</li>
</ul>
<ol start="3">
<li><p>공백 라인 (Empty Line)</p>
</li>
<li><p>응답 메시지 본문 (Request Message Body)</p>
</li>
</ol>
<p>⬇️<a href="https://brunch.co.kr/@swimjiy/7">[해치지 않는 웹] 3. 프록시 서버</a></p>
<h4 id="프록시proxy">프록시(proxy)</h4>
<ul>
<li>사전적 의미: 대리인</li>
<li>클라이언트와 서버 사이에서 HTTP 메시지를 대신 전달하는 중계 기능<h4 id="프록시-서버의-특징">프록시 서버의 특징</h4>
</li>
<li>클라이언트와 서버가 주고 받은적 있는 데이터의 사본</li>
<li>이전과 동일한 데이터를 요청하는 경우 서버를 거치지 않고 프록시에서 캐싱해둔 데이터를 반환하여 전송 시간을 줄일 수 있음</li>
<li>IP, 쿠키 등 HTTP 메시지에 신원을 확인할 수 있는 정보들을 제거하여 익명성 보호</li>
<li>유해 사이트에서 IP 추적 당하지 않게 우회하기 위한 용도로 사용된다는 문제점<h4 id="프록시-서버의-유형">프록시 서버의 유형</h4>
하나의 프록시 서버는 아래의 두가지 기능을 모두 수행할 수 있음</li>
<li>포워드 프록시 : 서버의 메시지를 클라이언트에게 전달 (forward 앞, 클라이언트로)</li>
<li>리버스 프록시: 클라이언트의 요청을 다수의 서버에 분배하여 전달. 어떤 서버에게 클라 요청을 전달하면 좋을지 확인<h4 id="via-헤더">via 헤더</h4>
메시지가 1개 이상의 프록시, 게이트웨이를 지나갈 시 via 목록 끝에 정보가 추가됨
최종으로 나열된 목록으로 클라이언트와 서버 사이의 중계기를 추적할 수 있다</li>
</ul>
<p>⬇️<a href="https://brunch.co.kr/@swimjiy/10">[해치지 않는 웹] 4.DNS</a>
IP (Internet Protocol)</p>
<ul>
<li>인터넷 장치 각각을 식별할 수 있는 고유한 주소</li>
<li>IP4와, IP4의 주소 고갈 문제를 해결하기 위해 생긴 IP6가 있음
도메인</li>
<li>외우기 힘든 IP 대신 사람이 기억하기 쉽게 만든 인터넷 주소</li>
<li>도메인: <a href="https://brunch.co.kr">https://brunch.co.kr</a></li>
<li>URL: <a href="https://brunch.co.kr/@vivid8822">https://brunch.co.kr/@vivid8822</a>
DNS</li>
<li>Domain Name System: 도메인 주소를 IP주소로 변환해줌</li>
<li>DNS서버, 네임서버 : 변환 시스템을 운영하고 있는 서버</li>
<li>DNS서버에 요청이 들어오면 도메인 주소와 연결되어있는 IP주소를 찾아 응답</li>
</ul>
<p>⬇️<a href="https://brunch.co.kr/@swimjiy/18">[해치지 않는 웹] 4.IP</a></p>
<ul>
<li>IP4: 총32비트, 2의 32제곱, 43억개의 고유주소 -&gt; 고갈문제 대두</li>
<li>IP6: 128비트, 거의 무한</li>
<li>&quot;새로운 IP체계쓰자&quot; vs &quot;이미 쓰고있는거 아껴쓰자&quot;</li>
<li>주소체계 전환 낮은 이용률</li>
<li>기존 IP4 고갈 지연 시키면서 통신 품질에 영향 없는 방안: 사설 IP주소와 공인 IP주소로 분리</li>
</ul>
<p>사설IP주소와 공인IP주소
라우터 : 건물, 고유한 주소인 공공 IP주소를 가짐
개인기기 : 건물 내 가구들, 하나의 라우터 영역에서 각자를 구분하기 위한 사설 주소를 가짐
WAN(Wide Area Network) : LAN을 연결한 광역 네트워크
LAN(Local Area Network) : 라우터 내에서 PC, 스마트폰 등의 기기들이 연결된 지역 네트워크
-&gt; 라우터에 여러 대의 기기를 연결함으로써 개인 PC에는 일일이 고유주소를 부여하지 않아도됨</p>
<p>공인: what is my ip
사설: 커맨드창에 ifconfig</p>
<p>NAT(network address translation): 라우터는 네트워크 주소 변환 기능을 탑재함.
라우터의 NAT기능으로 개인PC의 호스트 주소를 공인 IP로 변경하여 유투브 서버로 전달</p>
<p>⬇️ <a href="https://brunch.co.kr/@swimjiy/35">TCP</a>
패킷</p>
<ul>
<li>데이터 전송 시 패킷이라는 작은 조각으로 쪼개어 주고받음.</li>
<li>다양한 경로를 사용하여 이동 가능하고, 하나의 회선이 끊겨도 다른 패킷들이 안정적으로 들어올 수 있다.
TCP(Transmission Control Protocol)</li>
<li>데이터가 정확히 들어왔는지에 집중하는 규약</li>
<li>IP(비연결형)는 데이터를 정확한 주소로 전달하는 데에 목적, TCP(연결형, 수신자와 서로 연결된 상태인지 주기적으로 확인)는 데이터를 손실없이 안전하게 전달하는 것을 목적으로함</li>
<li>&quot;데이터에 대한 요청&quot; =&gt; &quot;요청받아 보내야하는 컴퓨터의 TCP 패킷으로 잘라 IP에 전달&quot; =&gt; &quot;IP는 패킷을 받아 주소 해석하고 경로 결정하여 목적지로 전송&quot; =&gt; &quot;받는쪽 IPsms 패킷 주소가 내 주소와 일치하는지 확인하고 맞으면 받는쪽 컴의 TCP로 전달&quot; =&gt; &quot;TCP는 패킷모아 데이터 재조립&quot;</li>
<li>TCP는 3 way handshake 기법을 통해 송신자와 수신자가 연결된 상태인지 확인한뒤 데이터 주고 받는다</li>
<li>전송속도가 느리다는 단점이 있음</li>
<li>유사한 역할을 수행하지만 속도는 빠른 UDP 프로토콜(연결 과정 없이 보내는 쪽이 일방적으로 데이터 전송, 온라인게임이나 VoIP라는 음성 데이터 주고 받을 시 사용)</li>
</ul>
<p>⬇️ <a href="https://brunch.co.kr/@swimjiy/39">그림으로 쉽게 보는 HTTP 변천사</a>
HTTP 0.9
HTTP 1.0 : 문서화 시작, 헤더 등장
HTTP 1.1 (1997) : 한번 TCP 연결을 맺으면 끊지 않고 유지, 한번에 여러개의 요청 보내게 함(Pipelining)
HTTP 2 : 이전 요청에 대한 응답 대기하지 않고 뒤의 응답 받을 수 있도록, 한번의 요청으로도 여러 응답 가능, 클라이언트가 요청하지 않아도 미리 리소스 푸시하여 더 높은 성능과 빠른 속도 보장 (텍스트 형식이었던 HTTP 메시지를 바이너리 형태로 캡슐화 하였기 때문에)
오래된 프로토콜인 TCP 위에 동작한다는게 단점
HTTP 3: QUID(Quick UDP Internet Connection), TCP가 아닌 UDP 위에서 동작하되 QUIC로 중간에 데이터 손실이 발생해도 개별적으로 재전송할 수 있게하여 신뢰성까지 개선</p>
<p>⬇️<a href="https://brunch.co.kr/@springboot/16">웹프로그래밍 스터디 - 1.HTTP 따라잡기(1)</a></p>
<ul>
<li>HTTP는 웹의 클라이언트 서버 통신을 위한 프로토콜</li>
<li>클라이언트와 서버는 요청, 응답을 수행</li>
<li>클라이언트는 한 번 응답을 보내면 자신이 보낸 응답 기억하지 않음</li>
<li>상태를 유지하기 위해서 cookie 등의 기술을 사용할 수 있음</li>
</ul>
<p>⬇️<a href="https://brunch.co.kr/@springboot/21">웹프로그래밍 스터디 - 2.웹서버 vs WAS</a></p>
<ul>
<li>웹서버 : HTML 이미지 등 정적 리소스 전달</li>
<li>WAS : 동적으로 동작하며 DB와 연동되고 비지니스 로직 포함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[호이스팅]]></title>
            <link>https://velog.io/@docu_a/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@docu_a/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Mon, 20 Mar 2023 06:54:42 GMT</pubDate>
            <description><![CDATA[<p>⬇️ <a href="https://developer.mozilla.org/ko/docs/Glossary/Hoisting">MDN 호이스팅</a></p>
<pre><code class="language-js">var num; // 선언
num = 6; // 초기화

// 예제 1
console.log(num) // var 호이스팅, undefined 출력
var num; // 선언
num = 6; // 초기화

// 예제2
console.log(num); // ReferenceError
num = 6; // 초기화

// 예제 3
// y만 호이스팅 대상, 초기화된 값2는 호이스팅되지 않음
x = 1; // x 초기화. x를 선언하지 않은 경우 선언. 그러나 명령문에 var가 없으므로 호이스팅이 발생하지 않음
console.log(x + &quot; &quot; + y); // &#39;1 undefined&#39;
// JavaScript는 선언만 호이스팅하므로, 윗줄의 y는 undefined
var y = 2; // y를 선언하고 초기화

// 예제 4
// 호이스팅은 없지만, 변수 초기화는 (아직 하지 않은 경우) 변수 선언까지 병행하므로 변수를 사용할 수 있음

a = &#39;크랜&#39;; // a 초기화
b = &#39;베리&#39;; // b 초기화

console.log(a + &quot;&quot; + b); // &#39;크랜베리&#39;</code></pre>
<p>⬇️ ★ <a href="https://evan-moon.github.io/2019/06/18/javascript-let-const/">TDZ</a></p>
<p>let, const 키워드로 선언한 리터럴 값은 호이스팅은 되나 특별한 이유로 인해 “초기화가 필요한 상태”로 관리되고 있다.</p>
<pre><code class="language-js">console.log(name); // undefined
var name = &#39;Evan&#39;;

console.log(aaa) // Uncaught ReferenceError: aaa is not defined

console.log(name); // Uncaught ReferenceError: Cannot access &#39;name&#39; before initialization
let name = &#39;Evan&#39;;</code></pre>
<blockquote>
<p><strong>선언 (Declaration)</strong>: 스코프와 변수 객체가 생성되고 스코프가 변수 객체를 참조한다.
<strong>초기화(Initalization)</strong>: 변수 객체가 가질 값을 위해 메모리에 공간을 할당한다. 이때 초기화되는 값은 undefined이다.
<strong>할당(Assignment)</strong>: 변수 객체에 값을 할당한다.</p>
</blockquote>
<p>var 키워드를 사용하여 선언한 객체의 경우 선언과 초기화가 동시에 이루어진다. 선언이 되자마자 undefined로 값이 초기화 된다는 것이다.</p>
<p>let 키워드나 const 키워드로 생성된 변수들이 TDZ(Temporal Dead Zone) 구간에 들어가는데 TDZ 구간에 있는 변수 객체는 선언은 돼있지만 초기화가 되지 않아 변수에 담길 값을 위한 공간 메모리에 할당이 되지 않게됨. 해당 변수에 접근을 시도하면 &quot;Cannot access &#39;%&#39; before initialization&quot; 에러 메시지 출력.</p>
<blockquote>
<p>TDZ의 보호를 받고 있지 않은 키워드인 var를 사용하는것은 변수의 값이 언제 바뀔지도 모르며 스파게티 코드를 만드는 주범이 될 수 있으므로 절대로 지양해야 합니다 😉</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[코어 자바스크립트 | 3장 this]]></title>
            <link>https://velog.io/@docu_a/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-3%EC%9E%A5-this</link>
            <guid>https://velog.io/@docu_a/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-3%EC%9E%A5-this</guid>
            <pubDate>Thu, 23 Feb 2023 10:27:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/docu_a/post/d6997f53-a75b-47b5-85a4-84c905f4e8f0/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/fcf19015-d5b4-4e5c-9332-6e6fe7121571/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코어 자바스크립트 | 2장 실행 컨텍스트]]></title>
            <link>https://velog.io/@docu_a/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-2%EC%9E%A5-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@docu_a/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-2%EC%9E%A5-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 23 Feb 2023 10:25:44 GMT</pubDate>
            <description><![CDATA[<p>함수 표현식이 안전하다</p>
<p><img src="https://velog.velcdn.com/images/docu_a/post/15a89826-a632-4706-aa14-86bafbbc7c61/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/2856d6be-7fd8-4cd9-b097-58e5af903725/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코어 자바스크립트 | 1장 데이터 타입]]></title>
            <link>https://velog.io/@docu_a/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-1%EC%9E%A5-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@docu_a/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-1%EC%9E%A5-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</guid>
            <pubDate>Tue, 21 Feb 2023 15:59:25 GMT</pubDate>
            <description><![CDATA[<p>하.................</p>
<p><img src="https://velog.velcdn.com/images/docu_a/post/09267351-bcbc-438e-ba9e-b05ae20e5f6b/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/3e0409e2-a0f3-4d70-a73c-b4a0e5ee15c2/image.jpg" alt=""><img src="https://velog.velcdn.com/images/docu_a/post/a9c4b878-d8b9-4ae9-a999-bcb908037b0f/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/681a8841-5ba9-4e30-8990-22569db7d4b4/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/f6e170c7-db08-47c6-b109-4fcb4e4e184f/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | WEEK2 | JS 기본 문법]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-WEEK2-JS-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-WEEK2-JS-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Wed, 15 Feb 2023 15:43:17 GMT</pubDate>
            <description><![CDATA[<p>내가 보려고 만든...뒤로갈수록 날아가는 글씨~</p>
<p><img src="https://velog.velcdn.com/images/docu_a/post/9b9c3840-c307-4ca9-b779-d1259f529c24/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/5883f13e-7872-4bfa-9dc8-121e734474ad/image.jpg" alt=""><img src="https://velog.velcdn.com/images/docu_a/post/37780ecf-2f07-4b6f-a564-7b727e2f0c2e/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/docu_a/post/e2e66dfb-5312-49e3-8deb-d74809e4b8f0/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | WEEK1 | 풀스택미니프로젝트]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-WEEK1-%ED%92%80%EC%8A%A4%ED%83%9D%EB%AF%B8%EB%8B%88%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-WEEK1-%ED%92%80%EC%8A%A4%ED%83%9D%EB%AF%B8%EB%8B%88%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Mon, 13 Feb 2023 14:58:05 GMT</pubDate>
            <description><![CDATA[<h3 id="시연영상">시연영상</h3>
<p><a href="https://youtu.be/DNkhCHsdcAQ">https://youtu.be/DNkhCHsdcAQ</a></p>
<h3 id="어려웠던-점">어려웠던 점</h3>
<ul>
<li>포스트할때 옵션태그에서 select한값 db에 저장할때. 토이프로젝트에서 라디오버튼 저장할때 배웠던 것과 비슷한게 있을 것 같아 스택오버플로우에서 답 발견</li>
</ul>
<pre><code class="language-js"> let selected = $(&#39;#star&#39;).find(&quot;:selected&quot;).text();</code></pre>
<ul>
<li>두가지 콜렉션을 참조해보는 것은 처음이었다. 계속 데이터는 잘 불러와지는데 페이지에 안떠서 당황했는데 알고보니 id값 넣는 자리 class값을 넣어서ㅎㅎ</li>
</ul>
<pre><code class="language-py">@app.route(&quot;/board/data&quot;, methods=[&quot;GET&quot;])
def board_get():
    board_list = list(db.board.find({}, {&#39;_id&#39;: False}))
    main_list = list(db.drama.find({}, {&#39;_id&#39;: False}))
    return jsonify({&#39;board&#39;:board_list,&#39;main&#39;:main_list})</code></pre>
<pre><code class="language-js">function show_comment() {
            $.ajax({
                type: &quot;GET&quot;,
                url: &quot;/board/data&quot;,
                data: {},
                success: function(response) {
                    let rows = response[&#39;board&#39;]
                    let row = response[&#39;main&#39;]
                    for (let i = 0; i &lt; rows.length; i++) {
                        let name = rows[i][&#39;name&#39;]
                        let comment = rows[i][&#39;bcomment&#39;]
                        let selected = rows[i][&#39;selected&#39;]
                        let temp_html = `&lt;div class=&quot;card-header&quot;&gt;
                                            ${selected}
                                          &lt;/div&gt;
                                          &lt;div class=&quot;card-body&quot;&gt;
                                            &lt;blockquote class=&quot;blockquote mb-0&quot;&gt;
                                              &lt;p&gt;${comment}&lt;/p&gt;
                                              &lt;footer class=&quot;blockquote-footer&quot;&gt;${name}&lt;/footer&gt;
                                            &lt;/blockquote&gt;
                                          &lt;/div&gt;`


                        $(&#39;#comment-list&#39;).append(temp_html)
                    }
                    for (let i = 0; i &lt; row.length; i++) {
                        let title = row[i][&#39;title&#39;]
                        let temp = `&lt;option&gt;${title}&lt;/option&gt;`
                        $(&#39;#star&#39;).append(temp)
                    }

                }
            });</code></pre>
<ul>
<li>느낀 점
  역시나 아직도 깃은 어렵다.. 그래도 이번에는 메인에서 다같이 풀하고 푸시하고 머지해서 큰 문제는 없었다
  로컬스토리지로 닉네임값을 저장해보고 싶었는데 템플릿과 부트스트랩이 꼬이는 문제로 시도를 못해봐서 아쉽다. 그래도 완성도 있게 잘 마무리한 것 같아 좋았다
  나를 제외한 팀원분들이 다 윈도우 유저여서 certifi 패키지 문제로 매번 내가 app.py를 수정하면 깃에서 충돌이 날 것 같아 두려워서 거의 화면 공유로 입코딩을 했는데 생각해보니 나만 쓰는 파이썬 파일을 하나 만들었으면 될 문제였던 것같다. 잠이 부족했어서 그랬나 왜 그게 생각이 안났을까...다음날 자고 일어나니 생각났다...</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해 99 | WEEK1 | 미니프로젝트 WIL]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B4-99-WEEK1-%EB%AF%B8%EB%8B%88%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-WIL</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B4-99-WEEK1-%EB%AF%B8%EB%8B%88%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-WIL</guid>
            <pubDate>Mon, 13 Feb 2023 14:31:31 GMT</pubDate>
            <description><![CDATA[<h3 id="구현할-때-어려웠던-기능">구현할 때 어려웠던 기능</h3>
<ul>
<li>나중에 메인화면에 문제집 제목의 리스트를 쭉 불러오는 것으로 했기 때문에 콜렉션에서 문제집하나의 고유의 값인 id값으로 문제집 콜렉션의 데이터를 가져와야한다고 판단했다. (다른 방법도 있지 않을까!) id 값을 콜렉션에서 가져올때 계속 type에러가 났다. 오브젝트 아이디와 관련된 타입 에러였는데 기술매니저님께 여쭤보고 4번째 줄로 str값을 oid로 바꾼거고 그것을 또다시 str으로 바꿔주는 작업이 필요했다는 것을 배웠다. (파이썬 파일의 4번째줄의 작업) 그리고 서버 쪽에서 render template하고 html에서 jinja2 문법으로 값을 넣을 부분을 지정해주는 방법을 알려주셨다.</li>
</ul>
<pre><code class="language-py">@app.route(&quot;/questions/&lt;id&gt;&quot;, methods=[&quot;GET&quot;])
def find_one_question(id):
    question = db.toy.find_one({&quot;_id&quot;: bson.ObjectId(oid=str(id))})
    question[&#39;_id&#39;] = str(question[&#39;_id&#39;])
    return render_template(&#39;seoa.html&#39;, question=question)</code></pre>
<pre><code class="language-html">&lt;div class=&quot;question&quot;&gt;
                &lt;div class=&quot;q_num&quot;&gt;1.&lt;/div&gt;
                &lt;div class=&quot;q_content&quot;&gt; {{ question.question_name }} &lt;/div&gt;
            &lt;/div&gt;

            &lt;div class=&quot;opt_container&quot;&gt;
                &lt;div class=&quot;option&quot;&gt;
                    &lt;input class=&quot;radio&quot; type=&quot;radio&quot; id=&quot;1-1&quot; name=&quot;one&quot; value=&quot;1&quot; onclick=&quot;answer_check(value)&quot;&gt;
                    &lt;label for=&quot;1-1&quot;&gt;① {{ question.question1 }}&lt;/label&gt;
                &lt;/div&gt;</code></pre>
<ul>
<li><p>db에서 정답인 번호를 가져와서 라디오 버튼에서 체크된 값과 비교하여 alert로 정답 여부 확인하는 기능 구현할때. 체크한 값을 db collection에 저장하고 다른 collection에서 저장되어있던 정답을 동시에 get 해야하는거라 생각해서 get과 post를 동시에 수행할 수 있는 api를 어떻게 만들지 고민이 있었다.
이후 시간 관계상 결과지를 만들지 않아도 돼서 db에 저장하지는 않아도 됐다(스택오버플로우에서 ajax로 radio button값을 저장하는 방법은 찾았으나 사용할일은 없었다ㅎ). 스크립트 api url 부분 링크에 오브젝트 아이디를 가져오는 것은 위에서 배운 jinja 문법과 유사했다. 기술매니저님께 여쭤보니 꼭 db에 값을 저장하지 않더라도 radio버튼에서 value값을 get 함수의 parameter로 불러와서 비교를 하는 방법을 알려주셨다!</p>
<pre><code>@app.route(&quot;/questions/&lt;id&gt;/check&quot;, methods=[&quot;GET&quot;])
def get_answer (id):
  question = db.toy.find_one({&quot;_id&quot;: bson.ObjectId(oid=str(id))})
  question[&#39;_id&#39;] = str(question[&#39;_id&#39;])
  print(question)
  answer_list = question[&#39;correctNum&#39;]

  return jsonify({&#39;answer&#39;: answer_list})</code></pre><pre><code class="language-html">function answer_check(value) {
          $.ajax({

              type: &quot;GET&quot;,
              url: &quot;/questions/{{ question._id }}/check&quot;,
              data: {},
              success: function(response) {

                  let correctNum = response[&#39;answer&#39;];

</code></pre>
</li>
</ul>
<pre><code>                if (correctNum == value) {
                    alert(&quot;정답입니다🥳&quot;)
                } else {
                    alert(&quot;오답입니다🥹&quot;)
                }
            }
        });
    }</code></pre><pre><code>* 마지막으로 어려웠던 기능은 메인화면에 저장된 문제집의 제목 값의 하이퍼링크를 쌓는 것이었다. 어떻게 문제집의 id값(전체)와 제목 전체를 가져와야할지에 대한 고민이 있었다. 매니저님께서 방법은 파이썬 파일에서 데이터를 가공하는 방식과 스크립트에서 가공하는 방식 두가지가 있다고 힌트를 주셨다. 파이썬 파일로 가공을 해서 jinja2 for문으로 구현할 수 있었다.
```py
@app.route(&#39;/&#39;)
def home():
    all_toy = list(db.toy.find({}))
    all_toy_ids = []
    all_toy_ids2 = []

    for value in all_toy:
        all_toy_ids.append(str(value[&#39;_id&#39;]))

    for value2 in all_toy:
        all_toy_ids2.append(value2[&#39;question_name&#39;])
    print(all_toy_ids)
    print(all_toy_ids2)

    return render_template(&#39;index.html&#39;, all_toy_ids=all_toy_ids, all_toy_ids2=all_toy_ids2, zip=zip)</code></pre><pre><code class="language-html">&lt;div class=&quot;link_box&quot; id=&quot;link_box&quot;&gt;
    &lt;p class=&quot;link&quot;&gt;○문제지의 해당란에 성명과 수험번호를 정확히 쓰시오.&lt;/p&gt;
    &lt;p class=&quot;link&quot;&gt;○수험번호는 ex)20230208 의 8자릿 수로 기입하시오.&lt;/p&gt;
    &lt;p class=&quot;link&quot;&gt;○답안지의 필적 확인란에 다음의 문구를 정자로 기입하시오.&lt;/p&gt;

    {% for ml, ml2 in zip(all_toy_ids2, all_toy_ids) %}
        &lt;div&gt;&lt;a href=&quot;/questions/{{ ml2 }}&quot;&gt; {{ ml }} &lt;/a&gt;&lt;/div&gt;
    {% endfor %}
&lt;/div&gt;</code></pre>
<h3 id="느낀-점">느낀 점</h3>
<ul>
<li>5시간동안 object id 타입에러로 씨름할때 같은 조 팀장님이 오류를 잡으면서도 계속 궁금해하고 탐구하려는 자세에서 정말 배울 점이 많았다.</li>
<li>jinja2 문법은 처음 배우게 되었는데 알아두면 꽤 활용도가 높았다. 검색해봤을때 꽤 예전 자료들이 나와서 요즘은 많이 쓰이지 않는 것 같았다.</li>
<li>웹종반 강의 자료는 보면 여러번 보면 볼 수록 구석구석 몰랐던 것들이 더 많이 보이는 것 같다.</li>
<li>첫 프로젝트부터 많이 어려운 도전이었지만 공부하는 자세면에서나 기술적인 면에서나 얻어가는 것이 많은 프로젝트였다.</li>
<li>아.. 깃은 참 어렵다...!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 | 웹개발 종합반 5주차]]></title>
            <link>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%9B%B9%EA%B0%9C%EB%B0%9C-%EC%A2%85%ED%95%A9%EB%B0%98-5%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@docu_a/%ED%95%AD%ED%95%B499-%EC%9B%B9%EA%B0%9C%EB%B0%9C-%EC%A2%85%ED%95%A9%EB%B0%98-5%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 06 Feb 2023 03:47:19 GMT</pubDate>
            <description><![CDATA[<h3 id="5-1">5-1</h3>
<p>filezilla: 클라우드 환경에 무엇을 올림, 파일을 보낼 수 있게 해주는 프로그램</p>
<p><a href="https://www.gabia.com">가비아</a>
개인으로 회원가입 -&gt;my가비아</p>
<p><img src="https://velog.velcdn.com/images/docu_a/post/52d7eea0-a96c-471d-bf85-49f942aca718/image.jpg" alt=""></p>
<p>5-5~5-6주차 버킷리스트는 한번 더 봐야할듯
업데이트</p>
<h3 id="5-7-내-플젝을-서버에-올리기">5-7 내 플젝을 서버에 올리기</h3>
<blockquote>
<ul>
<li>웹 서비스를 런칭하기 위해 클라이언트의 요청에 항상 응답해줄 수 있는 서버에 프로젝트를 실행시켜줄 거에요.</li>
</ul>
</blockquote>
<ul>
<li>언제나 요청에 응답하려면, 
1) 컴퓨터가 <strong>항상</strong> 켜져있고 프로그램이 실행되어 있어야하고, 
2) 모두가 접근할 수 있는 공개 주소인 공개 IP 주소(Public IP Address)로 나의 웹 서비스에 <strong>접근할 수 있도록</strong> 해야해요.</li>
<li>서버는 그냥 컴퓨터라는거 기억나시죠? 외부 접속이 가능하게 설정한 다음에 내 컴퓨터를 서버로 사용할 수도 있어요.</li>
<li>우리는 AWS 라는 클라우드 서비스에서 편하게 서버를 관리하기 위해서 항상 켜 놓을 수 있는 컴퓨터인 EC2 사용권을 구입해 서버로 사용할 겁니다.</li>
</ul>
<p>내 컴퓨터 계속 켜둘 수 없기 때문에 다른 사람의 컴퓨터를 빌려서 계속 올려둔다.
장점: 트래픽 대응도 쉬워지고, 서비스 붙이기 쉬워짐</p>
<p><a href="https://ap-northeast-2.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-2">EC2 콘솔페이지</a></p>
<p>인스턴스 시작 -&gt; Ubuntu Server 20.04 (프리티어 사용가능이라고 되어있으면 1년동안 한대 무료, 한개 끄고 한개도 가능, 두개 이상 실행하지 않으면 가능) -&gt; 검토 및 시작 -&gt; 시작하기</p>
<p>창이뜨면 -&gt; 새 키페어 생성 -&gt; 키페어 sparta_mykey 하고 키페어 다운로드 누르고 -&gt; 다운받으면 인스턴스 시작 누르기 -&gt; 인스턴스 목록에서 보면 상태 대기중에서 기다리면 실행중으로 바뀜(컴퓨터를 켠것과 마찬가지다)</p>
<p>내가 새로 하나 만들고 싶다 인스턴스를! 그러면 중지는 컴을 끄는 거고 종료가 반납임. 새로 하고싶으면 종료하고 인스턴스 시작 눌러서 다시 만들기.</p>
<p>컴을 켰으니 이제 접속!
터미널-&gt; sudo chmod 400 키페어 끌어놓고 엔터 -&gt; 맥 비번
ssh -i 받은키페어를끌어다놓기 ubuntu@AWS에적힌내아이피 (ssh로 접속하기)</p>
<blockquote>
<ul>
<li>SSH(Secure Shell Protocol)<ul>
<li>다른 컴퓨터에 접속할 때 쓰는 프로그램입니다. 다른 것들 보다 보안이 상대적으로 뛰어납니다.</li>
<li>접속할 컴퓨터가 22번 포트가 열려있어야 접속 가능합니다. AWS EC2의 경우, 이미 22번 포트가 열려있습니다. Mac OS: Mac은 ssh가 있어서, 명령어로 바로 접근 가능!</li>
</ul>
</li>
</ul>
</blockquote>
<p>리눅스는 마우스로 뭘 하는게 아니라 다 명령어를 내려서 하는것</p>
<pre><code class="language-shell">ls: 내 위치의 모든 파일을 보여준다.

pwd: 내 위치(폴더의 경로)를 알려준다.

mkdir: 내 위치 아래에 폴더를 하나 만든다.

cd [갈 곳]: 나를 [갈 곳] 폴더로 이동시킨다.

cd .. : 나를 상위 폴더로 이동시킨다.

cp -r [복사할 것] [붙여넣기 할 것]: 복사 붙여넣기

rm -rf [지울 것]: 지우기

sudo [실행 할 명령어]: 명령어를 관리자 권한으로 실행한다.
sudo su: 관리가 권한으로 들어간다. (나올때는 exit으로 나옴)</code></pre>
<h3 id="5-9-서버-세팅하기">5-9 서버 세팅하기</h3>
<p>구매한 서버에 파일을 올리고 서버를 실행하는 것</p>
<blockquote>
<p>서버 환경 통일. 우리는 지금 막! 컴퓨터를 구매한 상태예요. 여기에 이런저런 세팅들(업그레이드, DB설치, 명령어 통일 등)을 해줘야 본격적으로 이용할 때 편리하답니다!</p>
</blockquote>
<pre><code class="language-shell"># python3 -&gt; python
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10

# pip3 -&gt; pip
sudo apt-get update
sudo apt-get install -y python3-pip
sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1

# port forwarding
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000</code></pre>
<p>python3라고 하고 명령어를 원래 쳐야하는데 굳이 치지 말고 python이라고 하고 명령어를 쓴다는 뜻</p>
<p>flask, dnspython 깔때 패키지를 설치해줄 수 있는 pip라는 패키지 마법사같은 것을 설치한것</p>
<p>마지막 줄은 localhost:5000을 떼는 명령어</p>
<p>*filezilla 켜기
왼쪽이 우리 컴, 오른쪽이 내가 산 컴퓨터
파이참에서 홈워크 폴더 열고 
왼쪽 우리컴쪽에서 홈워크 들어간 상태에서
파일질라 파일 아래에 있는 사이트 관리자 그림 아이콘 클릭-&gt; new site-&gt;&#39;myec2&#39;-&gt; 오른쪽 일반&gt;프로토콜 에서 FTP를 SFTP로 바꾸고-&gt; 호스트는 aws 에서 인스턴스 세부정보에 있는 퍼블릭 아이피 붙여넣기-&gt;포트는 22번 -&gt; 로그온 유형 키파일-&gt; 사용자 ubuntu -&gt; pem files로 파일 이름 옆에 있는거 바꿔주면 키파일 보임 열기-&gt; 연결</p>
<p>shell로 만들어둔 폴더에 test.py나 실행하고 싶은 파일 옮겨 넣기</p>
<p>실행은 shell에서
ls -&gt; cd sparta -&gt; ls -&gt; python.test.py</p>
<p>venv 빼고 다른 파일 filezilla로 옮겨두고
shell에서 다시 실행해보기
ls -&gt; python app.py -&gt; pip install flask -&gt; pip install pymongo -&gt; pip install dnspython -&gt; python app.py -&gt;(실행됨 파이썬 콘솔창에 active 처럼 뜸)</p>
<p>aws에서 인스턴스페이지-&gt; 보안세부정보 클릭 -&gt; edit inbound rules -&gt; 규칙추가 -&gt; 포트범위 5000 -&gt;anywhere ipv4 -&gt; 규칙추가 (80도 열어주기) -&gt; 규칙저장</p>
<p>크롬에서 퍼블릭아이피:3000(포트번호) 이동 3000빼도 가능</p>
<blockquote>
<ul>
<li>지금은 5000포트에서 웹 서비스가 실행되고 있습니다. 그래서 매번 :5000 이라고 뒤에 붙여줘야 하죠. 뒤에 붙는 포트 번호를 없애려면 어떻게 해야할까요?</li>
</ul>
</blockquote>
<ul>
<li>http 요청에서는 80포트가 기본이기 때문에, 굳이 :80을 붙이지 않아도 자동으로 연결이 됩니다.</li>
<li>포트 번호를 입력하지 않아도 자동으로 접속되기 위해, 우리는 80포트로 오는 요청을 5000 포트로  전달하게 하는 포트포워딩(port forwarding) 을 사용하겠습니다.</li>
<li>리눅스에서 기본으로 제공해주는  포트포워딩을 사용할 것입니다.</li>
</ul>
<h3 id="5-11주차-nohup-설정하기">5-11주차 nohup 설정하기</h3>
<blockquote>
<ul>
<li>현재 상황<br>  Git bash 또는 맥의 터미널을 종료하면 (=즉, SSH 접속을 끊으면) 프로세스가 종료되면서, 서버가 돌아가지 않고 있습니다. 그러나 우리가 원격접속을 끊어도, 서버는 계속 동작해야겠죠?<ul>
<li>원격 접속을 종료하더라도 서버가 계속 돌아가게 하기
아래의 명령어로 실행하면 된다</li>
</ul>
</li>
</ul>
</blockquote>
<pre><code class="language-shell">    nohup python app.py &amp;

    서버 종료하기 - 강제종료하는 방법
    ps -ef | grep &#39;python app.py&#39; | awk &#39;{print $2}&#39; | xargs kill</code></pre>
<p>   터미널 끈 상태에서 다시 접속</p>
<pre><code class="language-shell">ssh -i 받은키페어를끌어다놓기 ubuntu@AWS에적힌내아이피 (ssh로 접속하기)</code></pre>
<p>ls -&gt; cd sparta-&gt; ls -&gt; python app.py</p>
<p>control+c 해서 끄고</p>
<pre><code class="language-shell"># 아래의 명령어로 실행하면 된다 (터미널꺼도 잘돌아감)
nohup python app.py &amp;

#강제종료
ps -ef | grep &#39;python app.py&#39; | awk &#39;{print $2}&#39; | xargs kill</code></pre>
<p>정리 app.py로 작업한 것을 배포하고싶을때는 venv빼고 올리고 nohup으로 켜주면됨
작업 못한거 있으면 꺼주고 파일질라에서 지우고 작업물 다시 파일질라에서 올리고</p>
<h3 id="5-11주차-도메인-연결하기">5-11주차 도메인 연결하기</h3>
<p>서비스 켜두고 도메인 열어야함
산 도메인 클릭-&gt; dns 설정 -&gt; 호스트이름 @ -&gt; ip주소는 숫자만 들어가게 -&gt; 확인-&gt;저장 -&gt; 거의 바로됨</p>
<p>가비아: 문자와 숫자를 연결하는 전화번호부 같은 것을 운영</p>
<h3 id="5-13주차-og태그">5-13주차 og태그</h3>
<pre><code class="language-html">&lt;meta property=&quot;og:title&quot; content=&quot;내 사이트의 제목&quot; /&gt;
&lt;meta property=&quot;og:description&quot; content=&quot;보고 있는 페이지의 내용 요약&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;이미지URL&quot; /&gt;</code></pre>
<p>카톡 입장에서는 공유할때마다 크롤링하려면 힘듦
한번 공유된것은 저장을 해둠
이미지 바꿨는데도 예전 이미지 나온다면</p>
<ul>
<li>페이스북 og 태그 초기화 하기: <a href="https://developers.facebook.com/tools/debug/">https://developers.facebook.com/tools/debug/</a></li>
<li>카카오톡 og 태그 초기화 하기: <a href="https://developers.kakao.com/tool/clear/og">https://developers.kakao.com/tool/clear/og</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>