<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ddonghyeo</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 13 Aug 2023 04:04:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ddonghyeo</title>
            <url>https://velog.velcdn.com/images/ddonghyeo_/profile/c70bacf0-4514-42c1-b99f-61d0be9b8a14/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ddonghyeo. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ddonghyeo_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[uWSGI] Django NGINX 연동]]></title>
            <link>https://velog.io/@ddonghyeo_/Django-uWSGI-NGINX-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@ddonghyeo_/Django-uWSGI-NGINX-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Sun, 13 Aug 2023 04:04:47 GMT</pubDate>
            <description><![CDATA[<p>한창 장고로 개발을 하고나면 runserver로 테스트를 했지만, 직접 배포를 runserver로 하게되면 보안, 안정성, 효율 등 여러 면에서 좋지않다.</p>
<ul>
<li>django.core.servers.basehttp<pre><code>&quot;&quot;&quot;
HTTP server that implements the Python WSGI protocol (PEP 333, rev 1.21).
Based on wsgiref.simple_server which is part of the standard library since 2.5.
This is a simple server for use in testing or debugging Django apps. It hasn&#39;t
been reviewed for security issues. DON&#39;T USE IT FOR PRODUCTION USE!
&quot;&quot;&quot;</code></pre></li>
</ul>
<p>장고에서도 runserver를 테스트나 디버깅 용으로 쓰라고 권장한다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a01188d6-dccb-4151-b201-cbf6c137c0dd/image.png" alt="">
 따라서, WS인 NGINX와 uWSGI를 통해서 연동시켜 보려고 한다.</p>
<p>NGINX와 같은 WS들은 python 프로그램을 해석할 능력이 없다.</p>
<p>python과같은 언어를 해석할 수 있는 인터페이스 역할을 하는 CGI가 필요하다.</p>
<p>이 때 WSGI는 웹 요청을 처리하는 표준 인터페이스이다.</p>
<p>보통은 WS + CGI + Django
에서 WSGI로 gunicorn이나 uWSGI를 많이 사용하는 것 같은데, gunicorn보다 uWSGI가 효율적인 면에서 좋은 것 같아서 uWSGI를 이용해보려고 한다.</p>
<p> <br><br><br></p>
<h1 id="1-uwsgi-설정">1. uWSGI 설정</h1>
<p> <br><br></p>
<h2 id="uwsgi-설치">uWSGI 설치</h2>
<pre><code class="language-shell">yum install -y gcc
 pip install uwsgi</code></pre>
<p>먼저 서버에 uWSGI를 설치해준다.
<br><br></p>
<h2 id="uwsgi-설정">uWSGI 설정</h2>
<p>설치를 완료했다면, 설정 파일을 만들어준다.</p>
<pre><code class="language-shell">mkdir -p /etc/uwsgi/sites
vi /etc/uwsgi/sites/django-project.ini</code></pre>
<p>위 파일명의 &#39;django-project&#39;에는 내가 시작한 장고 프로젝트의 이름을 넣어놓도록 하자.</p>
<pre><code class="language-shell">[uwsgi]
project = django-project #프로젝트 명
username = ec2-user #사용자
base = / #base 디렉토리

### Django Settings
# base directory
chdir = /home/ec2-user/django-project #장고 프로젝트 디렉토리
# python path
home = /usr #파이썬 홈 디렉토리
# virtualenv path
# virtualenv = /home/env/test #가상환경. 있다면 설정
# wsgi.py path
module = config.wsgi:application #장고 프로젝트 내 wsgi


master = true
processes = 5 #프로세스 수

uid = ec2-user
socket = /run/uwsgi/django-project.sock #소켓 전달 위치
chown-socket = root:nginx
chmod-socket = 660
vacuum = true

logto = /var/log/uwsgi/django-project.log</code></pre>
<p>정말 많은 사이트를 뒤져봤지만.. 나는 이렇게 설정해야 작동했다.</p>
<blockquote>
<ul>
<li>home의 경우 python 홈 디렉토리로 자동으로 설정된다. 만약 파이썬이 /usr/bin/python 경로에 있다면, /usr 만 설정해주면 uwsgi가 돌아가면서 자동으로 뒤에 /bin/python을 붙인다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>module의 경우 장고 프로젝트의 wsgi.py를 매핑해주면 된다. 내 프로젝트는 config 앱 내에 wsgi.py가 있었기 때문에 config.wsgi:application로 설정해줬다.</li>
</ul>
</blockquote>
<p>uWSGI를 구동하는 사이 나오는 로그는 logto의 디렉토리로 가게 된다. 해당 디렉토리를 만들어 주어야 한다.</p>
<pre><code>mkdir -p /var/log/uwsgi</code></pre><p><br><br></p>
<h2 id="uwsgi-서비스-작성">uWSGI 서비스 작성</h2>
<p>uWSGI가 서비스로 작동할 수 있게 설정해줘야 한다.</p>
<p>emperor모드에서 실행해줘야 한다.</p>
<pre><code class="language-shell">vi /etc/systemd/system/uwsgi.service</code></pre>
<pre><code class="language-shell">[Unit]
Description=uWSGI service

[Service]

ExecStartPre=/bin/mkdir -p /run/uwsgi

ExecStartPre=/bin/chown root:nginx /run/uwsgi

ExecStart=/home/ec2-user/.local/bin/uwsgi --emperor /etc/uwsgi/sites
Restart=always
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target</code></pre>
<p>리로드</p>
<pre><code class="language-shell">systemctl daemon-reload</code></pre>
<p>실행</p>
<pre><code>systemctl start uwsgi</code></pre><p><br><br><br><br></p>
<h1 id="2-nginx-설정">2. NGINX 설정</h1>
<p>이제 설치해둔 NGINX를 설정해주자.</p>
<pre><code> vi /etc/nginx/nginx.conf</code></pre><pre><code class="language-py">server{

        listen 8000;

        server_name {서버 도메인 또는 IP};



        location / {

                include uwsgi_params;

                uwsgi_pass unix:/run/uwsgi/django-proejct.sock;

        }

}</code></pre>
<p>서버에 맞게 설정해주면 된다.</p>
<p>uwsgi_params는 프로토콜 변수이다. 해당 변수를 include 해줌으로써 웹 서버가 전달하는 변수들을 
uWSGI서버가 동적으로 실행될 수 있다.</p>
<p>uwsig_pass는 uwsgi프로토콜을 이용해서 uWSGI에 서버 트래픽을 전달하고, ini파일에서 작성했던 unix socket을 사용한다.</p>
<p><br><br><br><br></p>
<h1 id="3-selinux">3. Selinux</h1>
<pre><code class="language-shell">vi /etc/sysconfig/selinux</code></pre>
<pre><code class="language-shell">SELINUX=disabled</code></pre>
<pre><code class="language-shell">reboot</code></pre>
<p>well-known포트를 사용하기 위해 해제해준다.</p>
<p>원래는 Selinux를 사용하지 않고 부분권한을 주는게 좋다.</p>
<p>하지만 이렇게 진행하겠다...
<br><br><br><br></p>
<h1 id="3-실행">3. 실행</h1>
<pre><code class="language-shell"> systemctl start uwsgi
 systemctl enable uwsgi</code></pre>
<p>uwsgi 서버를 실행하고, 8000포트로 접속하면 장고 앱 프로젝트가 실행된 것을 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NGNIX] 서버 SSL 설정]]></title>
            <link>https://velog.io/@ddonghyeo_/NGNIX-%EC%84%9C%EB%B2%84-SSL-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@ddonghyeo_/NGNIX-%EC%84%9C%EB%B2%84-SSL-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 08 Aug 2023 15:45:05 GMT</pubDate>
            <description><![CDATA[<p>ngnix SSL 인증 키가 있다면 다음과 같이 설정하자!!</p>
<p>80포트(HTTP)로 들어올 시 443(HTTPS)로 Redirect하게 설정했다.</p>
<p>추가로, .html 을 rewrite까지 설정했다.</p>
<ul>
<li>/etc/nginx/nginx.conf<pre><code class="language-py"># For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/
</code></pre>
</li>
</ul>
<p>user ec2-user;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;</p>
<h1 id="load-dynamic-modules-see-usrsharedocnginxreadmedynamic">Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.</h1>
<p>include /usr/share/nginx/modules/*.conf;</p>
<p>events {
    worker_connections 1024;
}</p>
<p>http {
    log_format  main  &#39;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &#39;
                      &#39;$status $body_bytes_sent &quot;$http_referer&quot; &#39;
                      &#39;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&#39;;</p>
<pre><code>access_log  /var/log/nginx/access.log  main;

sendfile            on;
tcp_nopush          on;
keepalive_timeout   65;
types_hash_max_size 4096;

include             /etc/nginx/mime.types;
default_type        application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;

server {
    listen       80;
    listen       [::]:80;
    server_name  domain.com;

    return 301 https://$host$request_uri;
}

# Settings for a TLS enabled server.

server {
    listen       443 ssl http2;
    listen       [::]:443 ssl http2;
    server_name  domain.com;
    root         /usr/share/nginx/html;

    ssl_certificate &quot;/etc/pki/nginx/certificate.crt&quot;;
    ssl_certificate_key &quot;/etc/pki/nginx/private/private.key&quot;;
    ssl_trusted_certificate &quot;/etc/pki/nginx/ca_bundle.crt&quot;;
    ssl_protocols TLSv1.2;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_ciphers PROFILE=SYSTEM;
    ssl_prefer_server_ciphers on;

    ##html rewrite
    location / {
            try_files $uri $uri.html $uri/ @extensionless-html;
     index index.html index.htm index.php;
    }

    location ~ \.html$ {
        try_files $uri =404;
    }

    location @extensionless-html {
            rewrite ^(.*)$ $1.html last;
    }

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;
    error_page 404 /404.html;
    location = /404.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }</code></pre><p>   }</p>
<p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] GitHub Action을 이용한 Amazon EC2 Spring 프로젝트 자동 배포]]></title>
            <link>https://velog.io/@ddonghyeo_/Docker-Amazon-EC2-%EC%97%90-Docker-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/Docker-Amazon-EC2-%EC%97%90-Docker-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Jul 2023 05:47:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/661f0593-aba6-4325-9f6e-d416f41cacde/image.png" alt="">
Amazon EC2 Linux환경에서 진행했다.
<br><br><br><br><br><br></p>
<blockquote>
<h3 id="ec2-docker-설치">EC2 Docker 설치</h3>
</blockquote>
<br>

<pre><code class="language-shell">sudo yum update -y</code></pre>
<p>시작하기 전에 yum 패키지를 모두 최신 버전으로 업데이트 해주었다.
<br><br><br></p>
<pre><code class="language-shell">sudo yum install docker -y</code></pre>
<p>yum을 이용해서 docker를 설치했다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/c9f82314-4998-43ca-99ef-922307284e41/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/36b35bde-772a-410c-9fda-fa7fe6a8fa1b/image.png" alt=""></p>
<ul>
<li>Docker 버전 확인<pre><code class="language-shell">docker -v</code></pre>
</li>
</ul>
<br>

<ul>
<li><p>Docker 시작</p>
<pre><code class="language-shell">sudo service docker start</code></pre>
<br>
</li>
<li><p>Docker 그룹에 ec2-user 를 넣어서 관리자 권한으로 실행할 수 있게 해준다.</p>
</li>
</ul>
<pre><code class="language-shell">sudo usermod -aG docker ec2-user</code></pre>
<br>

<ul>
<li>최신 버전의 Docker Compose 설치</li>
</ul>
<pre><code class="language-shell">sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose</code></pre>
<br>

<p>이후, 인스턴스 재 시작 후 Docker 명령어를 실행해보자.</p>
<pre><code class="language-shell">docker run hello-world</code></pre>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/6079784a-5dd5-49b2-b598-41ea5e9b57c4/image.png" alt=""></p>
<p><br><br><br><br><br><br></p>
<blockquote>
<h3 id="aws-iam-설정">AWS IAM 설정</h3>
</blockquote>
<p>AWS IAM 은 AWS의 기능들을 원격으로 사용할 권한이 있는 사용자를 관리하는 서비스이다.
<br></p>
<p>aws에서 IAM 서비스로 들어가서 사용자로 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/b445e839-3b08-4c6d-a22b-130044c2358b/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/5a8cabcd-cb41-4ca7-a5d7-ca0e173acb96/image.png" alt=""></p>
<p>사용자 추가를 눌러주자.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/ee0cc4ff-2ffb-47f2-b86d-472962695775/image.png" alt=""></p>
<p>원하는 이름을 입력하고 다음을 누른다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/89e01a63-a5f0-4443-a9e5-2bb8ed0194ab/image.png" alt=""></p>
<p>권한 설정 중에, 직접 정책 연결에서 EC2FullAcess를 검색하고 추가해준다.</p>
<p>그대로 사용자 생성을 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/6089d7b0-9660-41e5-a8bb-e284df6a9234/image.png" alt="">
사용자가 생성 되었다면, 눌러서 세부 설정을 해주자.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/83bf17f2-44ab-4026-9924-893ae8a59bf3/image.png" alt="">
아래에 보안 자격 증명 탭을 누르면, 엑세스 키 만들기 버튼이 있다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/7f10e14b-8e84-4170-9bc5-7a46323f7da3/image.png" alt="">
AWS 컴퓨팅 서비스에서 실행되는 어플리케이션을 선택하고 다음을 누른다.</p>
<p>원하는 태그를 입력하면, 엑세스 키가 발급된다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/ec55ca52-54cb-490c-94bd-ece2f9924829/image.png" alt=""></p>
<p>엑세스 키를 어느 곳에다가 복사해 두거나, csv파일을 받아놔서 소중히 잘 보관해두자.
다른 곳에(특히 github)에 절대로 유출되면 안된다!!</p>
<p><br><br><br><br><br><br></p>
<blockquote>
<h3 id="ec2-접근-설정">EC2 접근 설정</h3>
</blockquote>
<p>원래는 ssh를 이용해서 접근하는 방법을 사용할 수 있겠지만,
EC2 내에서 ID와 Password를 이용해서 접근하는 방법을 이용해 보겠다.
<br><br></p>
<pre><code>whoami</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/1a3b56e2-0bd6-4f04-9b0a-16532ec6b036/image.png" alt="">
보통은 ec2의 username은 ec2-user이다. 명령어를 이용해서 미리 확인해 보자.</p>
<p><br><br><br></p>
<pre><code class="language-shell">sudo passwd {username}

sudo passwd ec2-user</code></pre>
<p>위 명령어를 통해 새로운 비밀번호를 설정할 수 있다. 원하는 비밀번호로 설정해주자.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/2300efb5-1a33-455e-b972-877cfb4eacf5/image.png" alt=""></p>
<p><br><br>
이제, 비밀번호를 통해 접근하는 것을 허용해주자.</p>
<pre><code class="language-shell">sudo vi /etc/ssh/sshd_config</code></pre>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/748ea509-d831-43f8-8d6b-6f78e39a51bb/image.png" alt="">
편집기를 열어서 PasswordAuthentication 을 yes로 바꿔주자.
/Password 를 이용하면 쉽게 찾을 수 있다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/a9edc9e3-7fa5-44cc-b8d8-c18e8b23187e/image.png" alt=""></p>
<pre><code class="language-shell">sudo systemctl restart sshd </code></pre>
<p>설정하고 나면 sshd를 재시작 해준다.</p>
<p><br><br>
이제, ec2를 나와서(ctrl+D) 비밀번호로 접근이 되는지 확인해보자.</p>
<pre><code class="language-shell">sudo ssh [username]@[ec2 인스턴스 ip (퍼블릭 IPv4 DNS)]

sudo ssh ec2-user@ip</code></pre>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/098d437c-4c0e-4b4e-a4ff-3ea87f94d7ad/image.png" alt=""></p>
<p><br><br><br><br><br><br><br></p>
<blockquote>
<h3 id="docker-token-설정">Docker Token 설정</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/7ce842ce-b15e-41a4-90e9-7dbd765f7473/image.png" alt="">
Docker Hub에서 내 계정 설정에 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/5b18033a-1284-4183-98b1-36d211c0fa35/image.png" alt=""></p>
<p>Security부분에 New Access Token을 눌러서 토큰을 만들어 준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/de9eb288-dd83-4baf-a8c3-076e151f0822/image.png" alt="">
해당하는 이 토큰을 소중히 잘 갖고있자.</p>
<p><br><br><br><br><br><br><br></p>
<blockquote>
<h3 id="github-action-secret-설정">GitHub Action Secret 설정</h3>
</blockquote>
<p>위 Action 스크립트에서 사용했던 여러 키들을 Secret으로 관리할 수 있다.
Settings -&gt; Secrets and Variables -&gt; Actions에 들어와서
New repository secret을 눌러주자.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/82498b59-faa4-4752-a5d4-f7532e11c277/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/6087e33e-0421-4c04-bff3-7d63a8a85150/image.png" alt="">
여기서 여러 키들을 생성할 수 있는데, 다음 키들을 생성해 주자.</p>
<ul>
<li>DOCKERHUB_USERNAME : Docker Hub 닉네임 입력</li>
<li>DOCKERHUB_TOKEN : 방금 받았던 Docker Hub 토큰 입력</li>
<li>AWS_ACCESS_KEY_ID : 위 AWS IAM에서 발급받은 ACESS KEY를 입력</li>
<li>AWS_SECRET_ACCESS_KEY : IAM에서 발급 받은 SECRET KEY를 입력</li>
<li>AWS_SG_ID : EC2 보안그룹ID</li>
<li>EC2_HOST : EC2의 퍼블릭 IPv4 주소. </li>
<li>EC2_PASSWORD : EC2에 접근/연결 설정해두었던 패스워드.</li>
<li>EC2_SSH_PORT : SSH로 접근할 port. (22)</li>
<li>EC2_USERNAME : EC2의 계정명. (ec2-user)</li>
</ul>
<p>여기서 보안그룹ID는, 해당 인스턴스의 보안그룹에 들어가서 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/b9e0ed7e-2d5c-4b2e-9ce1-fadbd59d8e05/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/48dd7491-c4f3-41dd-a0bf-edd2a6890440/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/9a6b8f82-bd42-442c-9e70-ad4372ef7688/image.png" alt=""></p>
<p>모든 KEY들을 완성했다. 
이 KEY들은 GitHub Action의 스크립트 내에 알맞게 들어가서 실행 될 것이다.</p>
<p><br><br><br><br><br><br><br></p>
<blockquote>
<h3 id="프로젝트-docker-설정">프로젝트 Docker 설정</h3>
</blockquote>
<p>Spring 프로젝트 내에 Dockerfile을 만들어주자. <strong><em>파일 이름이 다르면 안됨!!</em></strong></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/0c2df344-d1a7-4b2b-b9da-c485484da58a/image.png" alt=""></p>
<p>아래 내용을 작성해준다.</p>
<ul>
<li>maven<pre><code class="language-shell"># Maven
FROM openjdk:11-jdk
</code></pre>
</li>
</ul>
<h1 id="jar-파일의-위치">Jar 파일의 위치</h1>
<p>ARG JAR_FILE=target/*.jar</p>
<h1 id="appjar는-경우에-따라-이름-변경">app.jar는 경우에 따라 이름 변경</h1>
<p>COPY ${JAR_FILE} app.jar</p>
<h1 id="생략-가능---해당-컨테이너는-8080-port-를-사용한다는-의미">생략 가능 - 해당 컨테이너는 8080 port 를 사용한다는 의미.</h1>
<p>EXPOSE 8080</p>
<h1 id="docker-run-시-실행할-필수-명령어">docker run 시 실행할 필수 명령어</h1>
<p>ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]</p>
<h1 id="경우에-따라-java-옵션-사용">경우에 따라 java 옵션 사용.</h1>
<h1 id="entrypoint-java--jar--dspringprofilesactivedocker-appjar">ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;-Dspring.profiles.active=docker&quot;, &quot;/app.jar&quot;]</h1>
<pre><code>
- gradle
```shell
FROM openjdk:11-jdk
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;, &quot;-Dspring.profiles.active=docker&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre><p>gradle이라면 build.gradle에 아래 내용도 추가해준다.</p>
<pre><code class="language-shell">jar {
    enabled = false
}</code></pre>
<p><br><br>
버전이 다른 내용이 있으면 알맞게 바꿔서 사용하면 된다!!</p>
<p>그리고 </p>
<pre><code class="language-shell">./gradlew build -x test</code></pre>
<p>명령어로 빌드를 한 번 해주자.</p>
<p><br><br>
<br><br>
<br><br></p>
<p><br><br><br><br><br><br><br></p>
<blockquote>
<h3 id="docker-이미지-생성">Docker 이미지 생성</h3>
</blockquote>
<p>이제, Docker에서 사용할 프로젝트 이미지를 생성해야 한다.</p>
<p>DockerHub의 닉네임으로 로그인할 수 있다.</p>
<pre><code class="language-shell">docker login -u [username]</code></pre>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/1ec67dea-0042-47d9-818e-9c5e3fc64fe1/image.png" alt=""></p>
<p>로그인에 성공하면 자동으로 비밀번호를 암호화하여 저장해둔다.</p>
<p><br><br><br><br><br><br></p>
<blockquote>
<h3 id="github-action-설정">GitHub Action 설정</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a137e657-61cc-4e5f-b5f0-59efb9bfdde3/image.png" alt="">
원하는 레포지토리 &gt; Actions에 들어와보자.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/438de1af-dde1-4154-8904-a57bb56c95b6/image.png" alt="">
여기서 Java with Gradle을 선택하면,</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a383f733-c8ae-4731-9301-e4b66ac66ba0/image.png" alt="">
gradle.yml 스크립트를 생성할 수 있다.</p>
<br>

<p>아래 내용을 작성해주자.</p>
<pre><code class="language-yml">name: Java CI with Gradle

on:
  push:
    branches: [ &quot;main&quot; ] #push할 경우 사용 할 branch 명 기입

jobs:
  deploy: 
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Set up JDK 17 
      uses: actions/setup-java@v2
      with:
        java-version: &#39;17&#39;  #Java 버전 기임
        distribution: &#39;temurin&#39;

    # Spring Boot Build
    - name: Spring Boot Build
      run: ./gradlew clean build -x test #test 제외 build

    # Docker Image Build
    - name: Docker Image Build
      run: docker build -t donghyunss/spring-docker . #image명 기입

    # DockerHub Login
    - name: Docker Hub Login 
      uses: docker/login-action@v2
      with: 
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    # Docker Hub push
    - name: docker Hub push
      run: docker push donghyunss/spring-docker

    # GET GitHub IP
    - name: Get GitHub IP 
      id: ip
      uses: haythem/public-ip@v1.2

    # Configure AWS Credentials - AWS 접근 권한 취득(IAM)
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with: 
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2 # AWS EC2 지역명 기입

    # Add github ip to AWS
    - name: Add GitHub IP to AWS
      run: |
        aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

    # AWS EC2 Server Connect &amp; Docker 명령어 실행 (8)
    - name: AWS EC2 Connection
      uses: appleboy/ssh-action@v0.1.6
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        password: ${{ secrets.EC2_PASSWORD }} 
        port: ${{ secrets.EC2_SSH_PORT }}
        timeout: 60s
        script: | #Docker Image명 기입, port 기입
          sudo docker stop spring-docker
          sudo docker rm spring-docker
          sudo docker run -it -d -p 8080:8080 --name spring-docker donghyunss/spring-docker

    # REMOVE Github IP FROM security group (9)
    - name: Remove IP FROM security group
      run: |
        aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32</code></pre>
<p><a href="https://lucas-owner.tistory.com/50">https://lucas-owner.tistory.com/50</a> 에서 빠진 부분과 몇 가지 수정을 했다.</p>
<p>Github AWS 연결 공식 문서 &gt;&gt; <a href="https://github.com/appleboy/ssh-action">https://github.com/appleboy/ssh-action</a></p>
<p>중간에 프로젝트에 맞는 Java 버전, Docker Image명, 원하는 port등을 수정해서 사용하면 되겠다.</p>
<p><br><br>
<br><br>
<br><br><br><br>
<br><br>
<br><br></p>
<blockquote>
<h3 id="push-테스트">Push 테스트</h3>
</blockquote>
<p>이제 깃허브에 푸시를 진행해보자.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/cc1a1265-3ece-4f9e-9b5d-dc6aaefc0870/image.png" alt=""></p>
<p>푸시를 진행하면, Action에서 push를 감지하고 커밋 메세지 명으로 된 Action을 시작하게 된다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/24a48451-4c32-4bb2-9ddf-690bc7ba9476/image.png" alt=""></p>
<p>그리고 Action이 모두 완료되면, 아래와 같이 성공으로 뜬다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/66f12942-429a-4752-8aec-d3933d7ac1c8/image.png" alt=""></p>
<p>여기서 오류가 난다면, 안으로 들어가서 어디가 오류 났는지 찾아보고 고쳐야한다...
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/b1c8e161-01b7-4b09-b502-178802d8bba1/image.png" alt="">
대부분은 스크립트 내에 설정 오류일 확률이 높다. gradle.yml 파일에 내가 넣은 설정이 맞는지 확인해보자.</p>
<p>만약 모든 스크립트가 성공했다면, 
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/414186e3-8206-4292-81fc-7dc81c29b6de/image.png" alt="">
Docker Desktop에 이미지가 정상적으로 push됐는지 확인해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SLASH23] 실시간 데이터 처리]]></title>
            <link>https://velog.io/@ddonghyeo_/SLASH23-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@ddonghyeo_/SLASH23-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Wed, 05 Jul 2023 13:45:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="시세-정보">시세 정보</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/df207601-ddfd-48f0-9deb-f1e7e59be95c/image.png" alt=""></p>
<p>시세 정보는 거래소로부터 실시간으로 정보를 받는다.</p>
<p>아무래도 시세 정보를 다루려면 <strong><em>실시간</em></strong>으로 다루어야 하기 때문에, </p>
<h3 id="낮은-지연시간-빠른-장애-복구">낮은 지연시간, 빠른 장애 복구</h3>
<p>를 우선시 해야한다.</p>
<p><br><br><br>
토스의 시세정보 처리 과정을 살펴보겠다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/ca29d70e-affd-4bed-a757-04382b5565c6/image.png" alt=""></p>
<ol>
<li><p>거래소로부터 받아오는 시세 정보를 UDP 멀티캐스트 그룹에 접속해서 읽어온다.
빠른 통신을 위해서 UDP를 사용하는 것 같다.
이 때, 헤더에 수신 시각을 포함하여 처리부에서 총 처리 시간을 측정한다.</p>
</li>
<li><p>처리부가 Redis에 저장하고, REST API를 제공한다.
처리부가 비지니스 로직이 가장 많기 때문에 장애 발생 확률이 가장 높다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/e6670130-3d10-41e2-9741-45e4ec3227e3/image.png" alt=""></p>
<p>따라서, 처리부를 여러개로 늘리는 방법을 선택했다.
평소에는 한 처리부(처리부 A)를 가동하다가, 장애가 발생하면 빠르게 처리부 B로 전환할 수 있다.</p>
<p>ZooKeeper를 통해 리더를 선출하고, 각 리더만 DB접근을 허용해 데이터 중복을 막을 수 있다.</p>
<p>이 방법은 배포 부담감도 줄일 수 있으며, 장애 발생을 대비할 수 있다.</p>
<p><br><br><br></p>
<p>하지만, 처리부가 늘어나면서 수신부가 보내야 하는 데이터의 부담감이 늘어났다.
처리부의 개수만큼 보내주어야 하기 때문이다.</p>
<p>이를 해결하기 위해 메시지 브로커를 사용한다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/4c74190f-728a-4d47-a341-5a94418390a8/image.png" alt="">
메시지 브로커를 통해 수신부와 처리부를 독립화하고, 수신부는 데이터를 한 번만 전송해도 된다.</p>
<p>그렇다면, 메시지 브로커가 지연시간을 줄이기 위해 선택할 목록을 살펴보자.</p>
<ol>
<li>UDP 멀티캐스트
보통 비디오 스트리밍, 게임 등 실시간 데이터를 사용하는 서비스에 많이 이용된다. 라우터 설정, 배포 설정이 필요하다.</li>
<li>Kafka
현업에서 많이 사용하는 분산형 데이터 스트리밍 플랫폼.</li>
<li>Redis Pub/Sub
Redis Publish/ Subscribe. 말 그대로, 특정한 주제를 구독한 수신자들에게 메시지를 발행하는 통신 방법이다.</li>
</ol>
<p>3번의 지연시간이 가장 낮아서 선택하게 됐다고 한다.</p>
<p><br><br><br></p>
<blockquote>
<h2 id="redis-pubsub">Redis Pub/Sub</h2>
</blockquote>
<p>Redis Publish/Subcribe
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/5d620f3e-619c-4d93-b1e6-54212888ec94/image.png" alt="">
Publish/Subscribe 구조에서 사용되는 Queue를 일반적으로 <strong><em>Topic</em></strong>이라고 한다. 
주제라고 생각하면 될 것 같다.</p>
<br>
보통은 채팅 기능이나, 푸시 알림 등에 많이 이용된다. 
단순하게 sender가 특정 데이터를 publish하면, sender를 subscribe한 receiver가 데이터를 받으면 되기 때문이다.

<p><br>이러한 pub/sub 시스템은 매우 단순한 구조로 되어있다.
메시지를 따로 보관하지 않으며, 상대가 메시지를 받았지를 확인하지도 않는다.</p>
<br>
Redis는 In-Memeory 기반이기 때문에 웹 소켓을 이용한 시스템보다 훨씬 빠르다.

<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/6c79da1a-7f4d-43ca-b4bc-f7121f1aaa32/image.png" alt=""></p>
<ul>
<li>Redis 명령어
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/76141eb4-740d-4d1a-8769-a23500d817da/image.png" alt="">
<a href="https://inpa.tistory.com/entry/REDIS-%F0%9F%93%9A-PUBSUB-%EA%B8%B0%EB%8A%A5-%EC%86%8C%EA%B0%9C-%EC%B1%84%ED%8C%85-%EA%B5%AC%EB%8F%85-%EC%95%8C%EB%A6%BC">https://inpa.tistory.com/entry/REDIS-%F0%9F%93%9A-PUBSUB-%EA%B8%B0%EB%8A%A5-%EC%86%8C%EA%B0%9C-%EC%B1%84%ED%8C%85-%EA%B5%AC%EB%8F%85-%EC%95%8C%EB%A6%BC</a></li>
</ul>
<p><br><br><br></p>
<blockquote>
<h2 id="event-loop">Event Loop</h2>
</blockquote>
<p>처리부에서는 Reid Pub/Sub과 TCP 연결을 맺으면서 TCP Flow Control을 수행함에 따라 많은 지연시간을 고려할 수밖에 없다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/ed4b6bbb-3e29-4fd0-b902-8d7dcbb48e97/image.png" alt=""></p>
<p>그렇기 때문에, 비지니스 로직을 포함하는 처리부와 데이터를 읽어오는 처리부에게 별도 스레드를 위임하는 것이 좋다.</p>
<p>그런데 이렇게 멀티 스레딩을 사용한다면, 시간 상 처리되어야 할 로직이 역전될 수 있다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/4b0ff961-7ba6-486e-a461-b7a2397bbcc5/image.png" alt=""></p>
<p>그래서 EventLoopGroup을 사용하여 Queue를 이용해 순서를 보장한 모습이다.
<br></p>
<h3 id="event-loop-구현">Event Loop 구현</h3>
<p>이 EventLoop는 Spring의 ThreadPoolTaskExecutor를 사용하고, 
corePoolSize = 1
maxPoolSize = 1
를 설정하여 만들 수 있다.</p>
<p>실시간성을 고려해서, Queue가 꽉 찰 경우 DiscardOldestPolicy을 채택하여 오래된 작업을 지우는 방식을 사용하는게 유리하다고 볼 수 있다. </p>
<p>QueueCapacity는 너무 크면 실시간성이 떨어질 수 있고, 너무 작으면 데이터가 몰리는 시간에 지연이 많아질 수 있다. 이에 따라 적당하게 조절해야 한다.</p>
<pre><code>List&lt;ThreadPoolTaskExecutor&gt;</code></pre><p>ThreadPoolTaskExecutor를 List 형태로 만든다.</p>
<p>여기서 List의 개수 = EventLoop 개수 를 뜻한다.</p>
<p>EventLoop의 개수가 
늘어날 수록 : 많은 Context Switch 성능 떨어짐
너무 작으면 : EventLoop에 BackPressure 발생, 지연 시간 늘어남</p>
<p>-&gt; 목표 트래픽을 발생시키며 모니터링 하여 적절한 수를 찾았다고 한다.</p>
<p><br><br><br></p>
<h3 id="lettuce">Lettuce</h3>
<p>Spring Data Redis가 제공하는 Redis Client 라이브러리</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/b2ec8f7d-32ee-4cb8-b7c1-18910f8db18f/image.png" alt=""></p>
<p>네트워크 라이브러리인 Netty를 사용하고, Netty의 Channel은 Socket을 추상화한 레이어이다.
커넥션이 맺어진 이후 EventLoop에 등록 된다.
Event Loop가 무한 루프를 돌면서 수신 버퍼의 데이터를 읽는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SLASH 23] PinPoint를 사용하여 분산추적하기]]></title>
            <link>https://velog.io/@ddonghyeo_/SLASH-23-PinPoint%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%B6%84%EC%82%B0%EC%B6%94%EC%A0%81%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/SLASH-23-PinPoint%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%B6%84%EC%82%B0%EC%B6%94%EC%A0%81%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 28 Jun 2023 14:06:37 GMT</pubDate>
            <description><![CDATA[<p>최근에는 많은 기업과 조직이 MSA형식으로 애플리케이션을 개발한다.</p>
<p>하지만 MSA 형식으로 개발한다면, 여러 분산된 서버들이 어떻게 동작하는지 한 눈에 살펴보기 어렵고, 전체 구조를 추적하기 어렵다.</p>
<blockquote>
<h3 id="pinpoint--분산추적">Pinpoint : 분산추적</h3>
</blockquote>
<p>네이버에서 만든 오픈소스 APM(애플리케이션 성능 모니터링)이다.</p>
<p>애플리케이션을 위한 모니터링 도구로, 서버의 트래픽 증가 등으로 인한 성능 감소, 병목 현상을 해결하기 위해 서버를 추적하여 확인할 수 있다.</p>
<p>특히, 분산 추적에 용이한 툴이다.</p>
<p>저장소는 HBase를 사용한다.
Hadoop의 HDFS위에 만들어진 분산 컬럼 기반의 비관계형 데이터베이스이다.
HBase의 Rowkey를 맞게 설계하면 데이터를 효율적으로 가져올 수 있다.
ex) 애플리케이션 이름 + 시간의 조합</p>
<p>Pinpoint 깃허브
<a href="https://github.com/pinpoint-apm/pinpoint">https://github.com/pinpoint-apm/pinpoint</a></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a3da5502-a942-406f-b449-5ff85f7a93d6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/aa6bacac-58c9-4f22-b049-9a065a9193d5/image.png" alt="">
출처 <a href="https://d2.naver.com/helloworld/1194202">https://d2.naver.com/helloworld/1194202</a></p>
<p>위와 같이 서비스 내의 요청과 정보를 확인해볼 수 있다.</p>
<p>이를 통해 유저가 어느 서비스에서 요청을 못 받았는지, 또는 어디서 지연이 됐는지 확인할 수 있다.</p>
<p><br><br><br><br></p>
<blockquote>
<h3 id="scatter-chart">Scatter Chart</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/ecaed16c-e530-4732-afff-699076c730ee/image.png" alt="">
PinPoint에서 확인할 수 있는 Scatter Chart이다.
해당 점은 외부에서 들어온 서비스 요청을 뜻한다.</p>
<p>각 점을 선택하면 시작 시간, Path, IP, TransactionID등 정보를 볼 수 있다.</p>
<p>Rowkey를 적절하게 설계했다면, 효율적으로 많은 데이터를 가져올 수 있다.</p>
<p>테이블에는 두 가지의 ColumnFamily가 있다.</p>
<ol>
<li>Index ColumnFamily
ScatterChart에서 점을 표현할 수 있는 정보를 가지고 있다. 이를 이용하여 Scatter Chart를 그리게 된다.</li>
<li>Meta Column Family
각 요청에 대한 정보들을 모아놓는다.
<br><br></li>
</ol>
<p>TransactionId는 요청을 받은 ApiGatway에서 만든다.
이 요청에 의해서 파생된 요청은 동일한 TransactionId를 가지게 된다.
TransactionId는 Meta ColumnFamily의 Qualifer에 TransactionId를 저장한다.</p>
<br>
이 TransactionId를 통해서 특정 시간의 요청을 조회하고,
동일한 TransactionId를 기준으로 유저 서비스가 호출된 요청을 찾아낼 수 있다.

<br>
이를 이용해서 요청 중간에 불필요한 요청을 추적하여 불필요한 시간을 줄일 수 있다.



<p><br><br><br><br><br></p>
<blockquote>
<h3 id="비동기-방식-처리">비동기 방식 처리</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/bcf944e0-60c5-4250-8d74-2319d3db4f76/image.png" alt=""></p>
<p>서버의 요청을 동기 방식으로 처리하는 것 보다, 비동기 방식으로 처리해서 서버에 머무르는 시간을 줄일 수 있고, 동시에 여러 연산을 처리할 수 있다.</p>
<p>이의 경우는 해당 요청이 동기 방식을 통해서 데이터의 return이 필요한지, 확인 과정을 거쳐야 하는지 판단해서 처리하는 것이 좋을 것이다.</p>
<p>코틀린의 경우, Coroutine을 이용해서 효율적인 비동기 방식의 요청을 할 수 있다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/b8d2598f-30f2-4943-b7fe-2e6d4fd0f878/image.png" alt="">
<a href="https://developer.android.com/kotlin/coroutines?hl=ko">https://developer.android.com/kotlin/coroutines?hl=ko</a></p>
<p>발표에선 코루틴이 PinPoint가 코루틴을 지원하지 않기 때문에, AsyncRestTemplate라이브러리와 ListenableFuture를 연결시켜 플러그인을 만들었지만, Spring 5.0 버전에서부턴 AsyncRestTemplate를 지원하지 않는다.</p>
<br>
따라서, WebClient를 통해 비동기 REST 를 보내는 예제를 살펴보자.

<pre><code class="language-java">import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

public class MyAsyncRestClient {
    WebClient client = WebClient.create();

    // 비동기 GET 요청 보내기
    Mono&lt;String&gt; responseMono = client
            .method(HttpMethod.GET)
            .uri(&quot;https://api.example.com/data&quot;)
            .retrieve()
            .bodyToMono(String.class);

    // 비동기 응답 처리
    responseMono.subscribe(
            response -&gt; {
                // 응답 받은 데이터 처리
                System.out.println(response);
            },
            error -&gt; {
                // 오류 처리
                System.err.println(&quot;Error: &quot; + error.getMessage());
            }
    );
}
</code></pre>
<p>Web Client를 이용하여 비동기 요청을 구성하고, subscribe 메서드를 통해 비동기 응답을 처리할 수 있다.
<br><br><br><br></p>
<blockquote>
<h3 id="스레드-풀-thread-pool">스레드 풀 Thread Pool</h3>
</blockquote>
<p>스프링 내부에서는 스레드 풀(Thread Pool)을 사용하여 비동기 처리와 관련된 작업을 수행한다.</p>
<p>스레드 풀을 이용해서 스레드를 관리하고 재사용하여 성능을 향상시킬 수 있다.</p>
<p>스프링에서 제공하는 TaskExcutor 인터페이스를 이용하여 태스크들을 비동기적으로 실행할 수 있다.</p>
<pre><code class="language-java">import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync //비동기 작업 수행 설정
public class AppConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); //Core Pool Size 조절
        executor.setMaxPoolSize(50); //Max Pool Size 조절
        executor.setQueueCapacity(100); //Queue 용량 조절
        executor.setThreadNamePrefix(&quot;ThreadPool-&quot;); //스레드 접두사
        executor.initialize();
        return executor;
    }
}
</code></pre>
<p><strong><em>@EnableAsync</em></strong> 어노테이션을 통해서 비동기 작업이 사용될 수 있도록 설정하고, 
<strong><em>@Async</em></strong> 어노테이션을 이용하여 비동기적으로 실행되는 메서드를 지정할 수 있다.</p>
<p><br><br><br><br>
이렇게 직접 설정해줄 수 있지만, 스프링 부트는 기본적으로 스레드 풀을 기본적으로 제공한다.</p>
<ul>
<li>properties<pre><code class="language-py"># 코어 스레드 수
spring.task.execution.pool.core-size=10
# 최대 스레드 수
spring.task.execution.pool.max-size=50
# 작업 큐 용량
spring.task.execution.pool.queue-capacity=100
# 스레드 이름 접두사
spring.task.execution.pool.thread-name-prefix=MyThreadPool-
</code></pre>
</li>
</ul>
<pre><code>- yml
```yml
spring:
  task:
    execution:
      pool:
        core-size: 10
        max-size: 50
        queue-capacity: 100
        thread-name-prefix: ThreadPool-
</code></pre><ul>
<li>core size : 풀 내에서 항상 유지되는 최소 스레드 수</li>
<li>max-size : 풀이 동시에 가질 수 있는 최대 스레드 수</li>
<li>queue-capacity : 대기열에 저장할 수 있는 작업의 최대 개수</li>
<li>thread-name-prefix : 생성된 스레드 이름의 접두사</li>
</ul>
<br>
스프링 부트는 기본적으로 스레드 풀을 지원하기 때문에, 이와 같이 스레드 풀을 설정해서 사용할 수도 있다.]]></description>
        </item>
        <item>
            <title><![CDATA[CORS]]></title>
            <link>https://velog.io/@ddonghyeo_/CORS</link>
            <guid>https://velog.io/@ddonghyeo_/CORS</guid>
            <pubDate>Wed, 28 Jun 2023 10:54:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/fb1239fb-750d-483f-bdda-07971402abc4/image.png" alt=""></p>
<blockquote>
<h2 id="cors란">CORS란?</h2>
</blockquote>
<h3 id="cross-origin-resource-sharing">Cross Origin Resource Sharing</h3>
<p>요청이 Same Origin, 즉 동일한 출처가 아닌 공유에 대한 접근을 막는 보안 정책이다.
요청을 한 도메인의 주소, 포트번호, 프로토콜이 모두 같아야지 해당 응답을 반응하는 정책이다.</p>
<p>예를 들어,
API서버의 도메인이 server.com이고,
요청한는 사이트의 도메인이 page.com이라면</p>
<p>요청을 하는 page.com의 도메인이 server.com의 도메인과 다르기 때문에 응답 요청을 막는 이유이다.</p>
<p><br><br><br><br></p>
<blockquote>
<h3 id="cors-정책의-이유">CORS 정책의 이유</h3>
</blockquote>
<p>만약, 다른 도메인의 응답을 처리할 수 있게 된다면,
다른 사이트를 접속했을 때, 해당 브라우저에서 다른 도메인의 요청을 받아 사용자의 인증 토큰을 받을 수 있기 때문이다.</p>
<p>예를 들어,
사용자가 example.com 에 접속을 했는데, 해당 사이트에서 <a href="http://www.naver.com%EC%97%90">www.naver.com에</a> 요청을 보내고,
<a href="http://www.naver.com%EC%97%90%EC%84%9C%EB%8A%94">www.naver.com에서는</a> 사용자에 대한 인증 토큰을 첨부해서 준다.</p>
<p>그렇다면 exmple.com에선 사용자의 naver에 대한 인증 권한을 받게될 수 있다는 것이다.</p>
<p><br><br><br><br></p>
<blockquote>
<h3 id="해결-방법">해결 방법</h3>
</blockquote>
<blockquote>
<h4 id="1-서버에서-설정하기">1. 서버에서 설정하기</h4>
</blockquote>
<p>쉬운 방법 중 하나로는, api 서버에서 따로 정책을 설정하는 것이다.</p>
<ul>
<li>Spring<pre><code class="language-java">@RestController
@CrossOrigin(origins = &quot;example.com&quot;) // 도메인에 대한 Origin 허용
public class MyController {
  @GetMapping(&quot;/example&quot;)
  public String example() {
      ...
  }
}</code></pre>
Spring에서 @CrossOrigin 어노테이션을 통해서 특정 도메인에 대한 Origin을 허용할 수 있게 된다.
위 코드에선 API서버가 example.com에 대한 CORS를 허용하는 것이다.</li>
</ul>
<p><br><br><br></p>
<ul>
<li>Django</li>
</ul>
<br>
django에선 django-cors-headers를 이용할 수 있다.

<pre><code class="language-python">pip install django-cors-headers</code></pre>
<p>해당 명령어를 통해서 django-cors-headers를 설치한다.</p>
<p><br><br></p>
<pre><code class="language-python">INSTALLED_APPS = [
    # ...
    &#39;corsheaders&#39;,
    # ...
]

MIDDLEWARE = [
    # ...
    &#39;corsheaders.middleware.CorsMiddleware&#39;,
    # ...
]
</code></pre>
<p>django settings.py에서 위 설정을 추가한다.
<br><br>
또는, settings.py에 특정 도메인을 허용할 수 있다.</p>
<pre><code class="language-python">CORS_ORIGIN_ALLOW_ALL = True</code></pre>
<p>위는 모든 도메인을 허용하는 설정이다.
<br></p>
<pre><code class="language-python">CORS_ORIGIN_WHITELIST = [
    &#39;http://example.com&#39;,
    &#39;https://example.com&#39;,
]</code></pre>
<p>위는 특정 도메인을 허용하는 설정이다.
<br><br><br><br></p>
<blockquote>
<h4 id="2-클라이언트에서-해결하기">2. 클라이언트에서 해결하기</h4>
</blockquote>
<p>CORS정책은 대부분 브라우저에서 막는 행위로 이루어지기 때문에, 브라우저에서 CORS정책 설정을 해주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/9d12151d-dbf7-4a21-ac95-c0e482d28105/image.png" alt="">
본 크롬 확장프로그램은 CORS 정책을 Unblock 해주는 프로그램이다.</p>
<p><br><br><br>
요즘은 API서버를 많이 사용하다 보니, 도메인이 달라서 CORS설정을 서버에서 해주는게 필수가 됐다 !!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vanilla JS] 바텀 시트(Bottom Sheet) 구현하기]]></title>
            <link>https://velog.io/@ddonghyeo_/Vanilla-JS-%EB%B0%94%ED%85%80-%EC%8B%9C%ED%8A%B8Bottom-Sheet-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/Vanilla-JS-%EB%B0%94%ED%85%80-%EC%8B%9C%ED%8A%B8Bottom-Sheet-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 20 May 2023 18:36:27 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/708816af-a9f9-45c8-bd82-c92acc1b18b8/image.gif" alt=""></p>
<blockquote>
<h2 id="html">HTML</h2>
</blockquote>
<pre><code class="language-html">&lt;div class=&quot;up_sensor&quot;&gt;&lt;/div&gt;
  &lt;div id=&quot;bottomSheet&quot; class=&quot;bottom_sheet&quot;&gt;
    &lt;div class=&quot;bottom_sheet_handle_wrap&quot;&gt;
      &lt;div class=&quot;bottom_sheet_handle&quot;&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div style=&quot;margin-bottom: 40px;&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;bottom_box&quot;&gt;
      &lt;!-- 바텀시트 내용 --&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
<p> up_sensor는 바텀시트를 올리기 위한 동작을 감지하기 위한 센서이다.
 실제 바텀 시트 위에 감싸고 있는 투명한 벽으로, 바텀시트가 올라가면 사라진다.
 센서를 두지 않으면 바텀 시트를 올리는 과정에서 스크롤 되면서 안의 내용이 스크롤 되는 것을 방지해서 만들었다.</p>
<p> bottom_sheet_handle은 사용자에게 바텀시트를 올리고 내릴 수 있는 hint를 제공한다.</p>
<p> <br> <br> <br></p>
<blockquote>
<h2 id="css">CSS</h2>
</blockquote>
<pre><code class="language-css">.bottom_sheet {
    display: flex;
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 10%;
    border-top-left-radius: 30px;
    border-top-right-radius: 30px;
    background-color: #FFFFFF;
    overflow-y: auto;
    flex-direction: column;
    align-items: center;
    transition-duration: 1s;
}

.bottom_sheet_handle_wrap {
    z-index: 1;
    background: linear-gradient(to bottom,
            rgba(255, 255, 255, 1) 20%,
            rgba(255, 255, 255, 0.75) 35%,
            rgba(255, 255, 255, 0.5) 60%,
            rgba(255, 255, 255, 0.25) 85%,
            rgba(255, 255, 255, 0) 100%);
    display: flex;
    justify-content: center;
    align-items: flex-start;
    width: 80%;
    height: 40px;
    margin-bottom: 10px;
    position: fixed;
}

.bottom_sheet_handle {
    width: 20%;
    height: 10px;
    background-color: #7c7979;
    border-radius: 30px;
    margin-top: 10px;
}

.bottom_box {
    width: 80%;
    height: auto;
    margin-top: 10px;
    margin-bottom: 10px;
    padding: 5px;
    border: 1px solid lightgray;
    border-radius: 10px;
    transition-duration: 2s;
    padding-top: 20px;
    padding-bottom: 20px;
}

.up_sensor {
    z-index: 1;
    position: absolute;
    background: transparent;
    width: inherit;
    height: 100%;
    bottom: 0;
    height: 10%;
    transition-duration: 1s;
}</code></pre>
<p>up_sensor는 바텀시트보다 z-index가 커야하고, background가 transparent여야 한다.</p>
<p>바텀시트의 height값을 중점으로 올리고 내리기 때문에, height값을 중점적으로 다뤄야 한다.</p>
<p>바텀 시트의 transition-duration값을 통해서 자연스럽게 올라가고 내려감을 표현해야 한다.</p>
<blockquote>
<h2 id="js">JS</h2>
</blockquote>
<pre><code class="language-js">
var handle_wrap = document.getElementsByClassName(&#39;bottom_sheet_handle_wrap&#39;)[0];
var bottom_sheet = document.getElementsByClassName(&#39;bottom_sheet&#39;)[0];
var up_sensor = document.getElementsByClassName(&#39;up_sensor&#39;)[0];
let bottom_touch_start = 0;
let bottom_scroll_start;

//up_sensor에서 터치가 움직였을 경우 (바텀시트를 건드렸을 경우) -&gt; 바텀시트를 올림
up_sensor.addEventListener(&quot;touchmove&quot;, (e) =&gt; {
  bottom_sheet.style.height = 70 + &quot;%&quot; //바텀시트 height를 올리기 10% -&gt; 70%
  up_sensor.style.height = 70 + &quot;%&quot;; //up_sensor도 따라가기
  setTimeout(function () {
    up_sensor.style.display = &quot;none&quot;;
  }, 1000); // 바텀시트가 올라간 후, up_sensor 사라지기
});


//맨 위에서 아래로 스크롤했을 경우
bottom_sheet.addEventListener(&quot;touchstart&quot;, (e) =&gt; {
  bottom_touch_start = e.touches[0].pageY; // 터치가 시작되는 위치 저장
  bottom_scroll_start = bottom_sheet.scrollTop //터치 시작 시 스크롤 위치 저장
});

bottom_sheet.addEventListener(&quot;touchmove&quot;, (e) =&gt; {
  //유저가 아래로 내렸을 경우 + 스크롤 위치가 맨 위일 경우
  if (((bottom_touch_start - e.touches[0].pageY) &lt; 0) &amp;&amp; (bottom_scroll_start &lt;= 0)) {
    //바텀시트 내리기
    bottom_sheet.style.height = 10 + &quot;%&quot;
    up_sensor.style.display = &quot;block&quot;; //up_sensor 다시 나타나기
    up_sensor.style.height = &quot;10%&quot;; //up_sensor height 다시 지정
  };
});


//맨 위 핸들을 아래로 당겼을 경우
handle_wrap.addEventListener(&quot;touchstart&quot;, (e) =&gt; {
  bottom_touch_start = e.touches[0].pageY; // 터치가 시작되는 위치 저장
});
handle_wrap.addEventListener(&quot;touchmove&quot;, (e) =&gt; {
  //사용자가 아래로 내렸을 경우
  if ((bottom_touch_start - e.touches[0].pageY) &lt; 0) {
    //바텀시트 내리기
    bottom_sheet.style.height = 10 + &quot;%&quot;
    up_sensor.style.display = &quot;block&quot;; //up_sensor 다시 나타나기
    up_sensor.style.height = &quot;10%&quot;; //up_sensor height 다시 지정
  };
});
</code></pre>
<p>사용자가 아래로 내렸는지 검사하는 과정 때문에 코드가 좀 길어진다.
<br>
바텀시트가 올라가는 경우 : </p>
<ol>
<li>사용자가 up_sensor를 위로 올렸을 경우 (실제 코드는 그냥 움직이기만 해도 올라간다.. 위로 올리는 경우로 구현해도 좋다)<br>
바텀시트가 내려가는 경우 :</li>
<li>스크롤 맨 위에서 아래로 스크롤 했을 경우</li>
<li>바텀시트 핸들을 아래로 내렸을 경우<br>
구글링을 해봐도 React로 구현한 경우가 대다수라 Vanilla JS로 구현해 보았다.
코드도 길고 복잡하지만 최대한 간결하게 써봤다!!!</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vanilla JS] html, css, js 슬라이더  구현하기]]></title>
            <link>https://velog.io/@ddonghyeo_/Vanilla-JS-html-css-js-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%8D%94-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/Vanilla-JS-html-css-js-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%8D%94-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 20 May 2023 18:16:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/db4eabcb-f13f-4f83-ad75-f404815768e3/image.gif" alt=""></p>
<blockquote>
<h2 id="html">HTML</h2>
</blockquote>
<pre><code class="language-html">  &lt;div class=&quot;slider_bar&quot;&gt;
    &lt;div class=&quot;slider_button&quot;&gt;
      &lt;span id=&quot;slider_per&quot;&gt;0%&lt;/span&gt;
      &lt;img src=&quot;{src}&quot; id=&quot;slider&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;</code></pre>
<p>간단하게 slider bar 위에 button을 놓았다. img는 button 안에 위치한다.
span 요소는 눌렀을 때 몇 퍼센트인지 나타내기 위함이다.
<br><br><br></p>
<blockquote>
<h2 id="css">CSS</h2>
</blockquote>
<pre><code class="language-css">.slider_bar {
  display: flex;
  position: fixed;
  right: 5%;
  top: 35%;
  bottom: 20%;
  width: 2%;
  height: 40%;
  background: linear-gradient(to bottom, 
    rgba(176, 196, 238, 1) 20%,
    rgba(176, 196, 238, 0.75) 35%,
    rgba(176, 196, 238, 0.5) 60%,
    rgba(176, 196, 238, 0.25) 85%,
    rgba(176, 196, 238, 0) 100%);
  border-radius: 20px;
  justify-content: center;
}

.slider_bar img {
  height: 30px;
  width: 30px;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -o-user-select: none;
  user-select: none;
}

.slider_bar span {
  display: none;
  position: absolute;
  left: -50px;
  width: 40px;
  height: 30px;
  border-radius: 10px;
  color: #FFFFFF;
  background-color: #8e8989;
  padding: 0 auto;
  align-items: center;
  justify-content: center;
  font-weight: 400;
  transition-duration: all 2s;
}

.slider_button {
  display: flex;
  width: 40px;
  height: 40px;
  position: absolute;
  bottom: 0;
  border-radius: 50%;
  background-color: #FFFFFF;
  text-align: center;
  font-weight: 900;
  color: gray;
  line-height: 7px;
  justify-content: center;
  align-items: center;

  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none
}</code></pre>
<p>css의 중요한 점은, slider button의 bottom값을 주어서 slider bar에서 얼마나 떨어져 있냐를 기록하는 것이다. 나머지는 자유롭게 설정하면 된다.
<br><br><br></p>
<blockquote>
<h2 id="js">JS</h2>
</blockquote>
<pre><code class="language-js">var slider_button = document.getElementsByClassName(&quot;slider_button&quot;)[0];
var percent = document.getElementById(&#39;slider_per&#39;);


//터치가 시작될 때
slider_button.addEventListener(&quot;touchstart&quot;, (e) =&gt; {
  percent.style.display = &quot;flex&quot;;  // 터치가 시작되면 퍼센트 보이기
  startPoint = e.touches[0].pageY; // 터치가 시작되는 위치 저장
});




//터치가 움직일 때
slider_button.addEventListener(&quot;touchmove&quot;, (e) =&gt; {

  //원래 bottom 값
  let origin = parseInt(getComputedStyle(slider_button).bottom.slice(0, -2));
  //최고 높이
  let max_height = parseInt(getComputedStyle(slider_bar).height);
  //시작 지점과 사용자 터치와의 사이 값
  diff = parseInt(startPoint - e.touches[0].pageY);

  //
  if (((origin + (diff - pre)) &gt; 0) &amp;&amp; ((origin + (diff - pre)) &lt;= max_height)) {
    slider_button.style.bottom = origin + (diff - pre) + &quot;px&quot;; // 원래 값 + (차이 - 이전 값)
    // + 슬라이더가 올라갈 때 생길 변화를 여기에
  }

  pre = diff; // 이전 값 저장

  // bottom 값
  let bottom_height = parseInt(getComputedStyle(slider_button).bottom);
  // 퍼센트 값 변경
  percent.innerText = parseInt(bottom_height / parseInt(max_height) * 100) + &quot;%&quot;;
});


//터치가 끝났을 때
slider_button.addEventListener(&quot;touchend&quot;, (e) =&gt; {
  pre = 0;   //이전 값을 0으로 초기화
  percent.style.display = &quot;none&quot;; //퍼센트 값 가리기
});</code></pre>
<ol>
<li>터치가 시작되면 시작된 값을 저장한다. <br></li>
<li>터치가 움직이면, 터치 시작 점과 현재 사용자가 터치하고 있는 Y값을 비교하고, 그 차이를 저장한다.</li>
<li>다음을 검사한다 :
3-1. 원래 bottom 값 + (차이 - 이전 값) 이 0보다 큰지
3-2. 원래 bottom 값 + (차이 - 이전 값) 이 최고 높이보다 작은 지
-&gt; 한 마디로 슬라이더 버튼이 있을 위치가 슬라이더 바 안에 있어도 되는지를 검사한다.</li>
<li>검사가 통과되면 슬라이더 버튼의 bottom 값을 원래 값 + (차이 - 이전 값) px만큼으로 설정한다.</li>
<li>이전 값 변수에 현재 값을 저장한다.<br></li>
<li>터치가 끝나면, 이전 값을 0으로 초기화하고, 퍼센트 값을 가린다.</li>
</ol>
<p>내 경험 상 이렇게 구현하는게 가장 깔끔하게 움직이고 작동하는 것 같다. 😂</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] MongoDB 적용, 테스트]]></title>
            <link>https://velog.io/@ddonghyeo_/SpringMongoDB.-NoSQL-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@ddonghyeo_/SpringMongoDB.-NoSQL-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sun, 30 Apr 2023 11:56:33 GMT</pubDate>
            <description><![CDATA[<p>*M1 Mac환경</p>
<blockquote>
<h3 id="mongodb-설치">MongoDB 설치</h3>
</blockquote>
<pre><code class="language-c">$ brew tap mongodb/brew
$ brew install mongodb-community</code></pre>
<blockquote>
<h3 id="mongodb-실행-및-정지">MongoDB 실행 및 정지</h3>
</blockquote>
<ul>
<li>서버 시작<pre><code class="language-c">$ brew services start mongodb-community</code></pre>
</li>
<li>서버 종료<pre><code class="language-c">$ brew services stop mongodb-community</code></pre>
</li>
</ul>
<p><em>서버를 시작하면 MongoDB의 기본 포트인 *</em>27017** 포트로 열린다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/e0e8d368-3f14-4f82-8411-c11eacdf3623/image.png" alt="">다음과 같이 뜨면 서버 구동에 성공한것 이다.</p>
<blockquote>
<h3 id="mongodb-터미널-활용하기">MongoDB 터미널 활용하기</h3>
</blockquote>
<pre><code>$ mongo</code></pre><p>mongo서버를 구동하고,
터미널에서 mongo를 입력하면 터미널에서 서버에 접속할 수 있다.</p>
<p>*zsh: command not found: mongo와 같은 오류가 발생해서,</p>
<pre><code>$ brew install mongodb-community-shell</code></pre><p>community shell을 설치해주었다.</p>
<ul>
<li>기본 명령어<pre><code class="language-c">// DB생성
use 데이터베이스
</code></pre>
</li>
</ul>
<p>//데이터베이스 보기
show dbs;</p>
<p>//컬렉션 생성
db.createCollection(&#39;컬렉션 명&#39;);</p>
<p>//컬렉션 보기
show collections;</p>
<p>//컬렉션 삭제
db.컬렉션명.drop();</p>
<p>//데이터베이스 삭제
db.dropDatabase();</p>
<pre><code>
![](https://velog.velcdn.com/images/ddonghyeo_/post/2a3eddc4-c819-4c2b-8bd3-118f6c6b1c52/image.png)
![](https://velog.velcdn.com/images/ddonghyeo_/post/1d415280-b97f-4c45-bf8a-43660eea2f4b/image.png)
![](https://velog.velcdn.com/images/ddonghyeo_/post/ea598b98-29ab-4d00-83f8-fca6e2c53c3c/image.png)
admin계정도 만들어 주었다.
&gt;### compass - MongoDB GUI 다운로드

https://www.mongodb.com/try/download/compass
![](https://velog.velcdn.com/images/ddonghyeo_/post/c7fdd571-2c69-40f6-a9e5-2085f213e091/image.png)
![](https://velog.velcdn.com/images/ddonghyeo_/post/531bb18b-afed-4ae9-865d-51873a721fa8/image.png)



&gt;### Spring에 적용시키기

1. build.gradle 작성</code></pre><p>implementation &#39;org.springframework.boot:spring-boot-starter-data-mongodb&#39;</p>
<pre><code>
위와 같이 dependency를 추가해준다.

2. application.yml 설정
``` yml
spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      authentication-database: admin 
      database: my-database
      username: DDonghyeo
      password: 1234</code></pre><ol start="3">
<li><p>Model 생성</p>
<pre><code class="language-java">@Getter
@Setter
@Document(collection = &quot;user&quot;)
public class User{

 private Long id;
 private String name;
 private String email;
}
</code></pre>
</li>
</ol>
<pre><code>
4. Repository 생성
``` java
public interface UserRepository extends MongoRepository&lt;User, String&gt; {

    public String findByName(String name);    
}</code></pre><p>userRepository 추상 메서드를 생성했다.
Spring에서 Data Repository에 한해 Naming Convention이 맞는 메서드에 대해
동적으로 data access에 대한 implementation을 제공한다.
따라서 실제로 findByName을 구현하진 않았지만, 조회가 잘 동작한다.</p>
<ol start="5">
<li><p>Service 생성</p>
<pre><code class="language-java">@Slf4j
@Component
public class UserSerivce {

 @Autowired
 UserRepository userRepository;

 public String findUserByName(String name){
     ObjectMapper objectMapper = new ObjectMapper();
     try {
         if (userRepository.findByName(name) == null){
             log.info(&quot;can&#39;t find user : {} &quot;, name);
             return String.format(&quot;can&#39;t find user : %s&quot;, name);
         } else {
             return userRepository.findByName(name);
         }

     } catch (JsonProcessingException e) {
         e.printStackTrace();
         return &quot;ERROR&quot;;
     }
 }

 public void addUser(User user){
     if(userRepository.findById(user.getId()) != null){
         log.info(&quot;user already exists.&quot;);
     } else {
         userRepository.save(user);
         log.info(&quot;user added completely&quot;);
     }
 }
}</code></pre>
<p>간단하게 이름으로 유저 찾기(findUserByName), 유저 등록하기(addUser) 메서드를 만들었다.</p>
</li>
<li><p>Controller 생성</p>
<pre><code class="language-java">@Slf4j
@RestController
@RequestMapping(&quot;/user&quot;)
public class UserController {

 @Autowired
 UserSerivce userSerivce;

 @GetMapping(&quot;/find&quot;)
 public String findUser(@RequestParam String name){
     return userSerivce.findUserByName(name);
 }

 @PostMapping(&quot;/add&quot;)
 public void addUser(@RequestBody User user){
     userSerivce.addUser(user);
 }
</code></pre>
</li>
</ol>
<p>}</p>
<p>```
/find에서는 name으로 사용자를 찾고, /add에서는 user를 등록할 수 있다.</p>
<blockquote>
<h3 id="실행-테스트">실행 테스트</h3>
</blockquote>
<h3 id="--유저-등록">- 유저 등록</h3>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a7a1109e-5bb5-4ffc-be83-4af0f12219c3/image.png" alt=""></p>
<p>먼저 서버를 돌리고, postman으로 테스트 해봤다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a16b9b47-b22b-4b3a-a411-7dc44904fcfa/image.png" alt=""></p>
<p>Body에 User를 담아서 Post한 결과 </p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/412e8b71-c2d2-4d1e-8d08-408fc86d8950/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/dbc6d870-a0f7-4880-b571-636536f3bf02/image.png" alt=""></p>
<p>설정해뒀던 로그가 잘 떴고, compass에서도 잘 확인됐다.</p>
<p>*중복 유저 등록 시
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/9e07c999-27d5-4ae2-8308-26369cc0d412/image.png" alt=""></p>
<h3 id="--유저-찾기">- 유저 찾기</h3>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/d0488d41-0890-41e5-ac55-5bf93715f4bb/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/b703c16a-b530-4efb-94eb-9aa456d4985b/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/8ff5d713-7d50-47e1-9918-4a904377c3bc/image.png" alt=""></p>
<p>모두 실행이 잘 되는 것을 볼 수 있다.</p>
<p>이로써 스프링 프로젝트에 NoSQL을 적용시켜서 써 봤는데,
NoSQL이 훨씬 편하고 보기도 좋은 것 같기도 하다 !!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[aws EC2, 가비아를 이용하여 Spring Project 직접 배포해보기 + SSL]]></title>
            <link>https://velog.io/@ddonghyeo_/aws-EC2-%EA%B0%80%EB%B9%84%EC%95%84%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Spring-Project-%EC%A7%81%EC%A0%91-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EA%B8%B0-SSL</link>
            <guid>https://velog.io/@ddonghyeo_/aws-EC2-%EA%B0%80%EB%B9%84%EC%95%84%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Spring-Project-%EC%A7%81%EC%A0%91-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EA%B8%B0-SSL</guid>
            <pubDate>Sat, 01 Apr 2023 13:53:04 GMT</pubDate>
            <description><![CDATA[<p>*실행 환경 M1 Mac</p>
<blockquote>
<h2 id="aws-ec2">aws EC2</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/e58a40f3-f2c4-452a-96a9-285fcc3de802/image.png" alt=""></p>
<p>aws에서 인스턴스 항목에서 인스턴스 시작을 누르자.</p>
<p>다른 항목은 다 건너 뛰고, 인스턴스 유형을 프리 티어 사용 가능한 t2.micro로 설정
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/bc7cf9db-0405-46c7-bd21-154b4216bf78/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/6b2d0057-ec0c-445d-a388-1e222f28802b/image.png" alt="">
&#39;새 키 페어 생성&#39;을 눌러서 새로운 키 페어를 생성해주었다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/b4ace008-6f53-409f-b6cb-02f74551862c/image.png" alt=""></p>
<p>원하는 이름을 설정하고 다운로드 받아지는 <em><strong>키이름.pem</strong></em> 을 절대로 유출하지 말고, 삭제도 하지 말고 잘 갖고있자.
PuTTY접속용이라면 ppk로 만들자.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/3f2dfa72-fcc7-4375-a53d-fff31aafa6fa/image.png" alt=""></p>
<p>나머지는 그대로 기본 설정을 따라간다.
원래는 SSH를 내 아이피만 허용해서 제대로 된 보안을 구축하는 것이 좋다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/51447479-6dec-4382-b5dd-d376dbbd5f99/image.png" alt=""></p>
<p>프리티어는 30GB까지 사용 가능하다. 하지만 잘못 설정하면 요금폭탄 맞으니 8GB만 하겠다..</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/8a19c090-ebc5-4aba-91d9-6ccc0e57269e/image.png" alt=""></p>
<p>정상적으로 인스턴스가 생성된 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/c2ddea63-87b6-4983-8e90-63c40182eac3/image.png" alt=""></p>
<p>왼 쪽에 탄력적 IP탭을 들어간다.<img src="https://velog.velcdn.com/images/ddonghyeo_/post/621d29e1-c045-42c3-a5f0-ee30d0433c85/image.png" alt="">
탄력적 IP 주소 할당을 눌러준다. 그리고 그냥 할당을 눌러주면 된다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/f3e1e6a7-810d-4760-b6e2-f41993b67206/image.png" alt="">
성공적으로 탄력적 IP를 할당 받았다. 이제 이 IP를 방금 생성했던 인스턴스에 연결해야 한다.
할당된 IP주소를 클릭해서 세부 설정에 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/9eda8a7a-0b05-4fa2-ab0b-6cdb456f9cff/image.png" alt="">
오른쪽 상단에 있는 탄력적 IP 주소 연결을 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/747a888a-d2e3-4a2b-aca0-85d934616c61/image.png" alt="">
인스턴스에서 방금 만들었던 인스턴스를 선택하고 연결을 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/3f53fed7-4fa0-4bea-b3d4-75f3395f64a2/image.png" alt=""></p>
<p>성공적으로 퍼블릭 IP가 연결된 것을 확인할 수 있다.</p>
<blockquote>
<h2 id="ssh-접속">SSH 접속</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/5d70d51c-86b7-4ad1-bc3f-83cb8ee9a589/image.png" alt="">
터미널을 켜서 .ssh 폴더로 이동한다.</p>
<pre><code class="language-cmd">$ vim config</code></pre>
<p>명령어를 통해 config파일을 만들어 준다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/de2cbe8b-ca13-4612-8243-02cd4e43e343/image.png" alt=""></p>
<pre><code>Host test
    HostName IP주소
    User ec2-user
        IdentityFile 키 경로</code></pre><p>를 작성해서 ssh접속을 자동화해준다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/8785ca26-a43a-4eee-9776-2fafab57ec7e/image.png" alt=""></p>
<pre><code>$ ssh 서버이름</code></pre><p>을 통해 ssh접속을 해준다.</p>
<p>Are you sure you want to continue connecting (yes/no/[fingerprint])? 가 나오면
yes를 입력하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/4bac46ae-7170-4cde-8200-673f756d3707/image.png" alt=""></p>
<p>그럼 이렇게 나오는데, 키의 권한이 없어서 그렇다.</p>
<p>터미널에서 키가 있는 경로로 가준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a705e319-ce4d-4c95-b8a0-d4e650bcb905/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/5831a40a-30e3-450b-b22b-5801ce3dbda7/image.png" alt=""></p>
<pre><code>$ chmod 600 키의 경로</code></pre><p>해당하는 키에 600권한을 주면 된다.</p>
<p>다시 ssh 접속을 시도해보았다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/5641d27c-c856-449a-b23a-70ee114ac01c/image.png" alt="">
성공적으로 서버에 접속을 완료했다.</p>
<blockquote>
<h2 id="spring-프로젝트">Spring 프로젝트</h2>
</blockquote>
<p>간이로 스프링 프로젝트를 만들어보자.
<a href="https://start.spring.io/">https://start.spring.io/</a></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/be02488f-f59f-4cd7-a101-83b79fcbbb18/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/9ec30f93-d23d-44cf-945c-f111bf6fe1c1/image.png" alt=""></p>
<p>테스트용으로 간이 프로젝트를 작성했다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/6e374092-5937-49ba-97e0-08dde11e8de7/image.png" alt="">
미리 80포트로 설정해 주었다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/9ff3affb-230f-478e-9112-b950dbf98360/image.png" alt="">
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/73896542-d562-4f1d-80ff-9c840a7738d3/image.png" alt=""></p>
<p>페이지 정상 작동 확인</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/c50340c1-115f-4a0e-a50a-2f0484d84f5f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/75e5de44-0dcd-4b77-965a-0f6a78653d87/image.png" alt="">
해당 프로젝트를 깃허브에 배포했다.</p>
<p>이제 서버에다 배포해보자.</p>
<blockquote>
<h2 id="서버-프로젝트-배포">서버 프로젝트 배포</h2>
<h4 id="java-다운로드">Java 다운로드</h4>
</blockquote>
<p>먼저, 서버에 Java를 설치하자.
<a href="https://jdk.java.net/">https://jdk.java.net/</a>
여기서 해당하는 Java 버전을 다운받자.</p>
<pre><code> $ sudo yum install java</code></pre><p>*sudo java install</p>
<pre><code>$ cd /usr/lib
$ wget jdk링크</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/651a0dc7-47af-49a1-bf15-6d0c59604807/image.png" alt="">
압축 풀기</p>
<pre><code>$ tar -zxvf 경로</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/7dc4050c-514d-47e8-a89f-c1809b821e55/image.png" alt=""></p>
<p>잘 설치되었나 확인</p>
<pre><code>$ /usr/lib/jdk-19.0.2/bin/java -version</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/cb5dba0e-5134-4e47-8667-60a9c293355e/image.png" alt=""></p>
<p>java 19가 잘 설치되었다.</p>
<p>이제 환경변수를 등록해주자.</p>
<pre><code>$ vi /etc/profile</code></pre><p>profile 수정을 들어가서, Shift + G 를 눌러 맨 아랫 줄을 들어가서, 다음을 작성한다.</p>
<pre><code>export JAVA_HOME=/usr/lib/jdk-자바 버전</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/aedf1c8a-1fb6-4d14-a0a4-c6b2c535c08e/image.png" alt=""></p>
<p>저장하고 나와서 다음을 실행해준다.</p>
<pre><code>$ source /etc/profile</code></pre><p>환경변수 등록이 완료됐다.</p>
<pre><code>$ echo $JAVA_HOME</code></pre><p>을 입력해 환경변수 등록이 잘 되었는지 확인해준다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/8bfb6665-11e2-4a61-84eb-3de00f113829/image.png" alt=""></p>
<p>다음은 Alias (별칭)등록을 해준다.</p>
<pre><code>$ vi /etc/bashrc</code></pre><p>마찬가지로 가장 아랫 줄에 두 줄을 추가해준다.</p>
<pre><code>alias java=&quot;/usr/lib/jdk-19.0.2/bin/java&quot;
alias javac=&quot;/usr/lib/jdk-19.0.2/bin/javac&quot;</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/68c30ca6-3a9e-4a58-af5a-29a3bcb7a96d/image.png" alt=""></p>
<pre><code>$ source /etc/bashrc</code></pre><p>수정한 파일을 저장해준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/ce567d49-5e9e-472b-9502-b5712418c1d1/image.png" alt="">
java 명령어가 잘 작동한다.</p>
<blockquote>
<h4 id="gradle-다운로드">gradle 다운로드</h4>
</blockquote>
<p>다음은 gradle을 설치해보자.
gradle 다운로드
<a href="https://services.gradle.org/distributions-snapshots/">https://services.gradle.org/distributions-snapshots/</a>
여기서 원하는 gradle 버전을 wget을 통해서 다운로드 한다.</p>
<pre><code>wget https://services.gradle.org/distributions/gradle-5.0-bin.zip -P /tmp</code></pre><p>압축 풀기</p>
<pre><code>$ sudo unzip -d /opt/gradle /tmp/gradle-5.0-bin.zip</code></pre><p>추출 확인</p>
<pre><code>$ ls /opt/gradle/gradle-5.0</code></pre><p>환경변수 설정</p>
<pre><code>$ sudo nano /etc/profile.d/gradle.sh</code></pre><p>다음 붙여넣기</p>
<pre><code># /etc/profile.d/gradle.sh

export GRADLE_HOME=/opt/gradle/gradle-5.0
export PATH=${GRADLE_HOME}/bin:${PATH}</code></pre><p>스크립트 실행 권한 설정</p>
<pre><code>$ sudo chmod +x /etc/profile.d/gradle.sh</code></pre><p>환경변수 로드</p>
<pre><code>$ source /etc/profile.d/gradle.sh
</code></pre><p>설치 확인</p>
<pre><code>$ gradle -v</code></pre><p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/040b63cd-bbce-4a95-8d39-297050c9bab8/image.png" alt="">
Gradle 설치 완료!</p>
<blockquote>
<h4 id="git-설치">git 설치</h4>
</blockquote>
<p>이제 git을 설치해서 내 프로젝트를 가져와보자.</p>
<pre><code>$ sudo yum install git</code></pre><p>간단하게 yum으로 다운로드 하자.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/ae2d3662-8638-485e-8731-b5e2a0a4c8de/image.png" alt="">
git 설치 완료.
이제 내 프로젝트를 git clone을 통해서 가져오자.</p>
<blockquote>
<h4 id="아파치-설치">아파치 설치</h4>
</blockquote>
<pre><code>$ sudo yum install httpd</code></pre><p>위 명령어를 통해 아파치를 설치할 수 있다.</p>
<pre><code>$ sudo systemctl start httpd</code></pre><p>-&gt; 아파치 실행 명령어</p>
<blockquote>
<h4 id="프로젝트-실행">프로젝트 실행</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/d9513794-c03c-4f7b-8da0-8399b8f93608/image.png" alt=""></p>
<p>먼저 clone을 통해 프로젝트를 가져왔다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/c7e36113-d75e-4074-a480-da9bff3b722c/image.png" alt="">
프로젝트 폴더에서 gradle 빌드를 시켜줬다.</p>
<ul>
<li>-x test 옵션 : test를 제외하고 빌드</li>
</ul>
<blockquote>
<h4 id="서버-배포">서버 배포</h4>
</blockquote>
<p><em>빌드 후에</em>
이제 /build/libs 폴더에 있는 스냅샷을 실행시켜준다.</p>
<pre><code>$ sudo nohup java -jar -Dspring.profiles.active=prod build/libs/demo-0.0.1-SNAPSHOT.jar &amp;
</code></pre><p>nohup -&gt; 백그라운드 실행 중 로그 추출
&amp; -&gt; 백그라운드 실행</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/38947d65-bd7a-4723-8906-9d67c621db3d/image.png" alt="">
퍼블릭 IP를 통해 접속은 확인됐다.</p>
<blockquote>
<h4 id="도메인-연결">도메인 연결</h4>
</blockquote>
<p>가비아 <a href="https://www.gabia.com/">https://www.gabia.com/</a> 에서 도메인을 구입했다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/f8c7bfbc-912b-433e-abd3-82eadaf28597/image.png" alt="">
도메인 관리 페이지로 들어가서, DNS 설정을 해준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/986bb81e-ba85-4241-8501-e3553a91288a/image.png" alt=""></p>
<p>해당 도메인의 레코드 수정을 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/1ece29d8-9bb2-4100-9bce-bd816b506f16/image.png" alt="">
다음과 같이 레코드를 설정해준다.</p>
<ul>
<li>@ : 앞에 www를 붙이지 않아도 접속할 수 있게 해준다. (dongpage.shop으로 접속할 때)</li>
<li>www : 앞에 www를 붙일 때 접속하게 해준다.</li>
</ul>
<p>이렇게 설정해주면, 도메인 설정이 완료된다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/bd5c93e8-2512-4625-8538-5b130569c2de/image.png" alt=""></p>
<p>아직 https설정이 되진 않았지만, 도메인을 통해 접속할 수 있다.</p>
<blockquote>
<h4 id="ssl-설정">SSL 설정</h4>
</blockquote>
<p>이제 내 페이지에 SSL인증서를 설치하여, 안전하게 보호할 차례이다.
SSL인증서를 설치하면, 서버 간의 통신에 암호화가 되기 때문에 중간의 통신 데이터를 가로채더라도, 어떤 값을 주고받았는지 알 수 없다. 그렇다고 <strong>서버가 안전한 것은 아님을 주의하자.</strong></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/10bb2729-ca57-4882-ba21-3414d3abc7e4/image.png" alt="">
<a href="https://manage.sslforfree.com/">https://manage.sslforfree.com/</a></p>
<p>SSL For Free라는 사이트에서 SSL인증서를 받을 것이다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/23f38705-0428-46e5-86e1-a803d030371d/image.png" alt=""></p>
<p>New Certificate를 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/c2eb0c49-4d91-408e-b59b-a91ac9bd7902/image.png" alt="">
내 도메인을 입력하고, Next Step을 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/e8be352c-aa14-4d39-b135-fdff1eca42fb/image.png" alt="">
무료 SSL인 90일제한 인증서를 눌러준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/4ef9163e-17dd-449b-bb86-928b4dc0c463/image.png" alt="">
Auto Generate를 선택하고 넘어간다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/bf5ca4d4-20ce-45ac-bd15-998b5cfa5232/image.png" alt="">
Free Plan이 선택되어 있는 것을 확인하고 넘어간다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/14a193a9-a9dc-4407-8eab-0965193780f6/image.png" alt="">
이제 이 도메인이 내 도메인인지를 확인할 시간이다.
나는 HTTP File을 업로드하는 방식을 선택할 것이다.
먼저 1번에 Download Auth File을 눌러 txt파일을 다운로드 해준다.</p>
<p>이 txt파일을 내 도메인/.well-known/pki-validation/ 파일에 넣어주어야 한다.
그러니까, 
<a href="http://dongpage.shop/.well-known/pki-validation/FEF7090FE1E9AE9E0DFF9FFEB3D7E825.txt">http://dongpage.shop/.well-known/pki-validation/FEF7090FE1E9AE9E0DFF9FFEB3D7E825.txt</a>
를 들어갔을 때 해당 txt파일이 뜨면 되는 것이다.</p>
<p>나는 이 txt파일을 웹 서버 Apache를 통해 업로드를 할 것이다.
Auth File을 다운로드 했으면 Next Step을 눌러주고 Apache 업로드를 해보자.</p>
<p>아파치는 간단하게</p>
<pre><code>$ sudo yum install httpd</code></pre><p>를 통해 다운받을 수 있다.</p>
<p>다운로드가 완료되면,</p>
<pre><code>/var/www/html/.well-known/pki-valdation</code></pre><p>경로에 txt파일을 넣어준다. (없는 폴더는 새로 만들어야 한다)
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/646f1790-77c0-41b1-821d-65f58cacb884/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/4fc8e627-b000-4209-82e6-a5f79e26a6e6/image.png" alt=""></p>
<p>파일을 넣었으면,</p>
<pre><code>$ sudo service httpd start</code></pre><p>명령어로 아파치를 실행해준다.
*반드시 80포트가 사용중이지 않아야 한다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/596b37f5-224a-41c1-a741-3dbdc4561350/image.png" alt=""></p>
<p>도메인에서 It Works!가 뜬다면 아파치 웹 서버가 정상적으로 실행된 것이다.
이제 해당 url에 들어가보면,
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/d9311ec4-2d4f-4ba8-b2ef-4b29ed391ac8/image.png" alt=""></p>
<p>txt 파일이 잘 뜨는것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/ffefe23e-a4fc-4d35-87b4-4f395b0c194c/image.png" alt=""></p>
<p>이제 Verify Domain을 눌러서 인증해주자.</p>
<pre><code>$ service httpd stop</code></pre><p>인증이 완료되었으면 위 명령어로 아파치 서버를 닫아준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/9634ee5b-e70e-4c8a-8677-d531802219a8/image.png" alt=""></p>
<p>이제 Server Type을 스프링 프로젝트를 배포할 것이므로, 내장 웹서버인 Tomcat을 선택하고 Download Certificate를 받아준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/36606210-9055-4772-9d0e-d289091a9d43/image.png" alt="">
압축을 풀면 세 가지 파일을 받을 수 있다.
Next Step을 눌러서 다음으로 넘어간다.</p>
<p>이제 서버에 SSL 설정을 적용시키기 전에, 
위에서 받은 세 파일을 이용하여 keystore을 만들어 주어야 한다.</p>
<p>먼저 openssl을 다운받자.</p>
<pre><code>$ sudo yum install opnessl</code></pre><pre><code>/etc/pki/tls/certs/test/certificate.crt
/etc/pki/tls/certs/test/ca_bundle.crt
/etc/pki/tls/private/private.key</code></pre><p>해당 경로에 세 파일을 넣어준다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/b6fc1b79-988b-439f-9dd6-02685127f0a5/image.png" alt=""></p>
<pre><code>$ openssl pkcs12 -export \
-name demo.test.me \
-in /etc/pki/tls/certs/test/certificate.crt \
-CAfile /etc/pki/tls/certs/test/ca_bundle.crt \
-inkey /etc/pki/tls/private/private.key \
-out /etc/pki/tls/certs/test/test.p12</code></pre><p>위 명령어로 keystore을 만들어준다.
*권한이 막히면 앞에 sudo를 붙여주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/b7282c95-a2e8-40fe-ad18-42a5bd8845c4/image.png" alt="">
원하는 비밀번호를 설정한다. (2번) &lt;- 비밀번호를 기억하고 있어야 한다!!</p>
<p>이제 서버 프로젝트 설정파일(application)에서 설정해주자.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/d2a7326b-c8bd-499e-8402-c0f232482fba/image.png" alt=""></p>
<pre><code>server:
    port: 443
    ssl:
        enabled: true
        enabled-protocols:
        - TLSv1.1
        - TLSv1.2
        key-store: &quot;/etc/pki/tls/certs/test/test.p12&quot;
        key-store-password: &quot;password&quot;
        key-store-type: &quot;PKCS12&quot;</code></pre><p>이제 443(https)로 열고, 아래처럼 설정해준다.
각 상황에 맞게 경로와 비밀번호를 바꿔주면 된다.</p>
<p>이제 서버를 다시 빌드하고, 실행하면</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/59d15c9a-0630-4556-9d8b-173d08e396ab/image.png" alt="">
443포트로 성공적으로 열리고
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/450c7489-b7d4-40a6-8a6d-4480b392ac87/image.png" alt="">
인증서 설정이 완료됐다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 환경에서 Redis 사용하기]]></title>
            <link>https://velog.io/@ddonghyeo_/Spring-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Redis-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/Spring-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Redis-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Mar 2023 15:16:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/fd5486a0-fa5a-4c5f-99c6-8d9e4da4d7a6/image.png" alt=""></p>
<blockquote>
</blockquote>
<h2 id="redis란">Redis란?</h2>
<p>Redis는 오픈 소스, 인 메모리 데이터 구조 스토어이며, 다양한 데이터 구조를 지원한다.</p>
<p>주요 용도로는 캐싱, 메시징, 세션 관리 등이 있다. Redis는 기본적으로 디스크가 아닌 메모리에 데이터를 저장하므로 매우 빠른 속도로 데이터를 처리할 수 있다. 또한 Redis는 영속성을 지원하여 디스크에 데이터를 저장할 수도 있다. Redis는 다양한 프로그래밍 언어로 작성된 클라이언트 라이브러리를 지원하며, 클러스터링 및 마스터-슬레이브 복제와 같은 고급 기능도 제공한다. Redis는 많은 어플리케이션에서 사용되고 있으며, 속도와 성능이 중요한 시스템에서 특히 유용하다.</p>
<blockquote>
</blockquote>
<h2 id="redis의-장단점">Redis의 장단점</h2>
<h3 id="장점">장점</h3>
<ul>
<li><p>빠른 데이터 액세스
Redis는 메모리에 데이터를 저장하기 때문에, 디스크에서 데이터를 읽어오는데 필요한 시간을 줄여 데이터 액세스 속도가 매우 빠르다.</p>
</li>
<li><p>다양한 데이터 구조
List, Set, Sorted Set, Hash 등의 데이터 구조를 제공한다.</p>
</li>
<li><p>Pub/Sub 메커니즘
Redis는 Pub/Sub 메커니즘을 지원한다. 이를 사용하여 메시지 큐, 실시간 채팅, 이벤트 처리 등 다양한 용도로 사용할 수 있다.</p>
</li>
<li><p>데이터의 영속성 보장
Redis는 데이터를 디스크에 저장할 수 있는 기능을 제공한다. 이를 통해 Redis가 비정상적으로 종료되거나 장애가 발생해도 데이터를 안전하게 보존할 수 있다.</p>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><p>메모리 제약
Redis는 메모리에 데이터를 저장하기 때문에, 메모리의 용량에 따라 저장할 수 있는 데이터의 양이 제한된다.</p>
</li>
<li><p>데이터의 일관성 유지
Redis는 메모리에 데이터를 저장하다가 Redis 서버가 장애가 발생하는 경우 데이터의 일부 또는 전체가 유실될 수 있다. 이를 방지하기 위해 Redis는 데이터를 디스크에 저장하는 기능을 제공하지만 이를 사용하면 데이터 액세스 속도가 느려질 수 있으며, 디스크의 손상 등 다양한 문제가 발생할 수 있다.</p>
</li>
<li><p>스케일 아웃 문제
Redis는 단일 스레드로 동작하므로, 대규모 데이터 처리를 위해 여러 Redis 인스턴스를 동시에 실행해야 한다. 이는 복잡한 구성과 관리를 필요로 하며, 이로 인한 부하 및 지연 문제도 발생할 수 있다.</p>
</li>
</ul>
<blockquote>
</blockquote>
<h2 id="spring에서-redis-사용하기">Spring에서 Redis 사용하기</h2>
<h4 id="1-의존성-추가">1. 의존성 추가</h4>
<p>Maven환경</p>
<pre><code>&lt;dependency&gt;
    &lt;groupId&gt;redis.clients&lt;/groupId&gt;
    &lt;artifactId&gt;jedis&lt;/artifactId&gt;
    &lt;version&gt;3.6.1&lt;/version&gt;
&lt;/dependency&gt;
</code></pre><p>Gradle 환경</p>
<pre><code>dependencies {
    implementation &#39;redis.clients:jedis:3.6.1&#39;
}</code></pre><h4 id="2-redis-설정">2. Redis 설정</h4>
<p>application.properties에 다음을 추가한다.</p>
<pre><code>spring.redis.host=localhost
spring.redis.port=6379
</code></pre><p>위에는 Redis 서버의 IP주소, 아래는 포트 번호를 입력하면 된다.</p>
<h4 id="3-redis-사용하기">3. Redis 사용하기</h4>
<p>Redis를 사용하기 위해서는 RedisTemplate 클래스를 사용한다.
여기서 RedisTemplate을 사용하기 위해서 RedisConnectionFactory를 설정해야 한다.</p>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class MyRedisService {

    @Autowired
    private RedisTemplate&lt;String, String&gt; redisTemplate;

    //데이터 저장하기
    public void saveData(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 데이터 가져오기
    public String getData(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 애플 로그인 구현하기 ]]></title>
            <link>https://velog.io/@ddonghyeo_/Spring-%EC%95%A0%ED%94%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/Spring-%EC%95%A0%ED%94%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Mar 2023 13:00:39 GMT</pubDate>
            <description><![CDATA[<p>소셜 로그인을 구현하는 방법은 비슷하지만, Apple의 경우 방식이 조금 다르다.
Apple에서는 identity token을 사용하기 때문이다.</p>
<blockquote>
<h3 id="준비과정">준비과정</h3>
</blockquote>
<h2 id="1-apple-developer-계정">1. Apple Developer 계정</h2>
<p>먼저 애플로그인을 구현하기 위해선 Apple Developer 계정이 필요하다. 
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/56311368-c524-490a-b639-60e73289629f/image.png" alt="">
Apple Developer Program에 가입하려면 $99 (약 129,800원)이 필요하다.
혼자 연습용으로는 조금 부담되는 가격이다. 🥲</p>
<h2 id="2-app-id-발급">2. App ID 발급</h2>
<p>애플 Certificates, Identifiers &amp; Profiles 탭 - Identifiers에서 플러스 버튼을 눌러 앱ID를 만들어준다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/990732e3-03a5-4c4b-a269-49ef6aada6d0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/66efe42d-2ba2-40ee-ac78-be4fba935210/image.png" alt=""></p>
<p>앱 ID를 선택하고 다음을 누른다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/ac387bd8-26d3-4734-a980-92076edaeebe/image.png" alt="">
App을 선택한다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/c22d8e3d-b7b8-4076-9c94-08e79b45e1df/image.png" alt="">
Descripttion과 Bundle ID를 입력해준다.
보통 Bundle ID에는 패키지를 쓴다. ex) com.demo</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/56667fef-f7bc-4069-b779-281dfbdf5f06/image.png" alt=""></p>
<p>아래에서 Sign in with Apple을 선택하고 Edit를 누른다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/30fd833a-8afe-44e5-9fd7-cced242a4ab0/image.png" alt="">
서버의 앤드포인트를 입력하고 완료하면 App ID가 생성된다.</p>
<h2 id="3-apple-key-발급">3. Apple Key 발급</h2>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/60267786-da0b-4dc9-bc2e-914b6b965f61/image.png" alt="">
Keys 탭으로 와서 플러스 버튼을 눌러 새로운 키를 발급받는다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/33460978-bde3-4bd4-a236-d8b1c0c4df33/image.png" alt=""></p>
<p>원하는 키 이름을 쓰고, 아래 Sign in with Apple을 체크하고 Configure을 누른다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/c39016b7-9399-45bd-b0e8-9f1e4eb02cb0/image.png" alt="">
방금 등록했던 App ID를 넣으면 된다.</p>
<p>이후 다음으로 넘어가면 <strong>AuthKey_[KeyID].p8</strong> 파일을 받을 텐데, 재 다운로드가 안되니
소중하게 보관해두고, 유출하지 말자. (github에 올리지 않도록 주의.)</p>
<h2 id="4-service-id-등록">4. Service ID 등록</h2>
<p>다시 Identifiers탭으로 돌아와서, 새로 하나 만들어준다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/5f69a2cc-4357-4abc-a090-3a0039a29ac2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/aeebbfa8-1199-4fd1-b444-f93284da7c9d/image.png" alt="">
이번엔 Service IDs를 선택하고 다음을 누른다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/091e4d79-11a7-4b93-afa5-e85aeac78a57/image.png" alt=""></p>
<p>Description과 indentifier (Bundle ID 역순)을 입력하고 다음을 누른다.
다음엔 도메인 설정이 나오는데, </p>
<p>Domains and Subdomains - 서비스 도메인</p>
<p>Return URLs - 콜백받을 url
을 채워 넣는다.
localhost는 들어가지 않으므로 .shop등 도메인을 하나 구매하는 방법을 써도 된다.
http도 들어가지 않아서, 인증서 설정도 해줘야한다.</p>
<blockquote>
<h3 id="구현">구현</h3>
</blockquote>
<pre><code class="language-java">@Configuration
@EnableOAuth2Client
public class OAuth2ClientConfig {

  @Value(&quot;${spring.security.oauth2.client.registration.apple.client-id}&quot;)
  private String clientId;

  @Value(&quot;${spring.security.oauth2.client.registration.apple.client-secret}&quot;)
  private String clientSecret;

  @Value(&quot;${spring.security.oauth2.client.registration.apple.redirect-uri}&quot;)
  private String redirectUri;

  @Bean
  public OAuth2AuthorizedClientManager authorizedClientManager(
      ClientRegistrationRepository clientRegistrationRepository,
      OAuth2AuthorizedClientRepository authorizedClientRepository) {

    OAuth2AuthorizedClientProvider authorizedClientProvider =
        OAuth2AuthorizedClientProviderBuilder.builder()
            .authorizationCode()
            .refreshToken()
            .build();

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
        new DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository);

    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
  }

  @Bean
  public ClientRegistrationRepository clientRegistrationRepository() {
    return new InMemoryClientRegistrationRepository(this.appleClientRegistration());
  }

  private ClientRegistration appleClientRegistration() {
    return ClientRegistration.withRegistrationId(&quot;apple&quot;)
        .clientId(this.clientId)
        .clientSecret(this.clientSecret)
        .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .redirectUri(this.redirectUri)
        .scope(&quot;email&quot;)
        .authorizationUri(&quot;https://appleid.apple.com/auth/authorize&quot;)
        .tokenUri(&quot;https://appleid.apple.com/auth/token&quot;)
        .userInfoUri(&quot;https://appleid.apple.com/auth/userinfo&quot;)
        .userNameAttributeName(IdTokenClaim.SUB)
        .jwkSetUri(&quot;https://appleid.apple.com
</code></pre>
<p>Apple 로그인 페이지에서 인증 정보를 입력하면, 애플에서 발급한 인증 토큰을 받아와서 Spring Security OAuth2가 이를 사용하여 사용자 정보를 가져온다.
가져온 사용자 정보를 사용하여 로그인을 처리하거나 새로운 계정을 생성하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[현업에 뛰어들기 전, 꼭 알아야 할 소프트웨어 프로세스 정리 - 소프트웨어 개발 생명주기]]></title>
            <link>https://velog.io/@ddonghyeo_/%ED%98%84%EC%97%85%EC%97%90-%EB%9B%B0%EC%96%B4%EB%93%A4%EA%B8%B0-%EC%A0%84-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%A0%95%EB%A6%AC-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/%ED%98%84%EC%97%85%EC%97%90-%EB%9B%B0%EC%96%B4%EB%93%A4%EA%B8%B0-%EC%A0%84-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%A0%95%EB%A6%AC-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Wed, 15 Mar 2023 14:06:38 GMT</pubDate>
            <description><![CDATA[<p>오늘 학부 수업을 들으면서 프로세스에 관한 내용을 들었는데, 너무 유익한 내용인 것 같아서 정리하려고 한다.</p>
<p>프로젝트를  잘 성공시키려면 개발의 퀄리티뿐만 아니라, <strong>개발문서</strong>에도 시간을 투자해야 한다.</p>
<blockquote>
<h3 id="프로세스란">프로세스란?</h3>
<p>주어진 목적을 위해 수행되는 일련의 절차
&quot;내가 뭘 해야될지 정해져 있는 목차&quot;</p>
</blockquote>
<p>현업에서 프로젝트를 진행할 때, 어떤 절차로 진행되는지 꼭 알아야 할 것이다.
사이드 프로젝트여도, 어떤 프로젝트냐에 따라 맞는 프로세스를 선택하여 효율적으로 프로젝트를 진행해야 한다.
프로세스를 잘 선택하면,</p>
<ul>
<li>소프트웨어의 개발 목표가 명확해진다.</li>
<li>프로젝트의 실패를 막을 수 있다.</li>
<li>소프트웨어의 품질은 프로세스의 품질에 의해 결정된다.</li>
</ul>
<blockquote>
<h2 id="소프트웨어-개발-생명주기-software-development-life-cycle">소프트웨어 개발 생명주기 (Software Development Life Cycle)</h2>
<p>소프트웨어를 어떻게 개발할 것인가?에 대한 추상적 표현</p>
</blockquote>
<blockquote>
<h3 id="주먹구구식-개발-모델build-fix-model">주먹구구식 개발 모델(Build-Fix Model)</h3>
</blockquote>
<p>보통 학부에서 진행하는 프로젝트의 대부분의 경우이다. 설계 없이 일단 개발에 들어가는 것을 말하며, 크기가 매우 작은 규모의 프로젝트에 적용된다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/3545e7ed-9278-4853-a568-104f5a33925b/image.png" alt="">
Ad-hoc (그때그때 다르게)라고도 하며, 정의되어있는 프로세스가 없다.
이 모델은 작은 프로젝트라면 충분히 잘 활용할 수 있을 것이다.</p>
<blockquote>
<h3 id="폭포수-모델-waterfall-model">폭포수 모델 (Waterfall Model)</h3>
<p>폭포처럼 떨어지는 모습의 모델</p>
</blockquote>
<p><strong>보통 소프트웨어 개발의 전형적인 개발 모델이다. **
소프트웨어 개발의 전 과정을 나누어 체계적이고 순차적으로 접근하는 방법이다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/2af042cb-9eb4-482e-9e82-1a6ef06da872/image.png" alt="">
여기서 주의할 점은, 위 -&gt; 아래의 순서를 지키되, 다시 전 단계로 돌아가지 않는다.
예를 들어, **설계 단계가 끝나고 구현 단계 중에서,변동사항이 생겼다고 해서 다시 설계로 넘어가지 않는다.</strong> (물론 돌아갈 때도 있지만, 비용이 많이 든다.)
만약 변동사항이나 피드백사항이 있다면 다음 프로젝트에 반영한다.</p>
<ul>
<li>요구사항 분석
개발하려는 소프트웨어의 요구사항을 수집하고 문제를 이해하고 분석한다.</li>
<li>설계
프로그램의 전반적인 구조, 알고리즘 등 시스템의 구조를 결정한다.</li>
<li>구현
설계 명세서를 기반으로 실제 모습으로 변환시킨다.<ul>
<li>테스트
프로그램이 입력대로 잘 작동하는지 테스트를 수행하고, 문서화한다.</li>
<li>유지보수
소프트웨어의 변경사항을 적용하고, 수정, 기능추가 등을 수행한다.</li>
</ul>
</li>
</ul>
<p>폭포수모델은 가장 정형화되어있고, 진행을 명확하게 할 수 있지만 단계가 확실하게 나뉘어져 있어 앞 단계가 완료될때까지 기다려야하는 단점이 있다.
또한, 고객 입장에서  소프트웨어 구현을 늦게 확인할 수 있어 요구사항 확인에 많은 시간이 걸린다.</p>
<blockquote>
<h3 id="원형모델-prototyping-model">원형모델 (Prototyping Model)</h3>
<p>점진적으로 개발해나가는 모델</p>
</blockquote>
<p>폭포수모델의 단점을 보완한 모델이다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/625d1228-4a3e-428d-bc19-f008a299b87e/image.png" alt="">
원형을 만들어 고객과 사용자가 함께 평가하는 방식이다.</p>
<ul>
<li>폐기 프로토타입(Throw away Prototype)
고객으로부터 피드백을 받은 후 원형을 폐기한다.</li>
<li>진화적 프로토타입
중요한 부분만 구현한 뒤, 지속적으로 발전시켜 완제품을 제작한다.</li>
</ul>
<p>보통은 고객의 요구사항을 완전히 파악하기 어려울 때 사용하는 모델이며, 빠른 설계가 특징이다.
초반에 <strong>프로그램의 신뢰도나 품질이 아니라, 가능한 빨리 원형을 구현하는 것</strong>이 핵심이다.</p>
<blockquote>
<h3 id="나선형-모델-sprial-model">나선형 모델 (Sprial Model)</h3>
<p>위험을 관리하고 최소화하는 모델</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a7780fcf-72ed-45e3-adb2-20bf9bbf4d15/image.png" alt="">
폭포수 모형의 장점 + 원형 모형의 장점 + 위험 분석을 추가한 모델이다.
개발이 <strong>점증적</strong>으로 진행된다는게 특징이다.
여러 개의 작업 영역으로 구분하여, 중간중간 위험 분석을 주기적으로 진행하여 위험 관리를 특화한다.</p>
<p>보통 고비용의 시스템이나, 시간이 많이 드는 큰 프로젝트에 사용되는 모델이다.
위험 분석을 통해 프로젝트의 실패 가능성을 낮출 수 있지만, 상대적으로 다른 모델보다 복잡해서 관리가 어렵다.</p>
<blockquote>
<p>프로세스의 중요성?</p>
</blockquote>
<p>프로세스를 공부하고 나서, 좋은 프로세스는 곧 좋은 프로젝트로 연결되고, 곧 좋은 회사로 연결된다는 것을 많이 배웠다. 아는만큼 보인다는 말처럼, 나중에 내가 좋은 경험을 할 수 있을지에 대한 판단의 근거에 꼭 프로세스 관찰을 포함하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증(Authentication)과 인가(Authorization)]]></title>
            <link>https://velog.io/@ddonghyeo_/Spring-%EC%9D%B8%EC%A6%9DAuthentication%EA%B3%BC-%EC%9D%B8%EA%B0%80Authorization</link>
            <guid>https://velog.io/@ddonghyeo_/Spring-%EC%9D%B8%EC%A6%9DAuthentication%EA%B3%BC-%EC%9D%B8%EA%B0%80Authorization</guid>
            <pubDate>Wed, 08 Mar 2023 13:36:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="인증authentication과-인가authorization">인증(Authentication)과 인가(Authorization)</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/7bf10d64-08f0-482f-a025-7dc6b920bbe7/image.png" alt=""></p>
<p>인증과 인가는 조금씩 다르다.
인증(Authentication)은 사용자가 서버로부터 자신이 누구인지 알리는 과정이다.
그림처럼 로그인 과정이 그에 해당하는데, 로그인을 통해 유저가 서버의 회원임이 인증되면, 서버에서 access token과 refresh token을 발급받는다.</p>
<p>인가(Authorization)는 인증 된 사용자가, 로그인을 이미 거친 상황이므로 더이상 로그인의 과정을 반복하지 않기 위해, 특정 기능을 접근할 때 이미 인증된 사용자임을 밝히며 기능을 요청하는 과정을 말한다.
사용자가 특정 기능을 서버에 요청할 때, request header에 accesstoken을 함께 보내면, 서버에서 accesstoken이 유효한지(서명, 만료기간 등) 검사한 후에 기능을 제공한다.</p>
<p>이때, 기능마다 다른 권한을 부여하기 위해 Role을 통해 사용자의 권한을 구분한다.</p>
<p>만약 access token이 기간이 만료됐을 경우, 사용자가 access token과 refresh token을 같이 서버에 보내면, 서버에서 refresh token의 유효성을 검사한 후에, 새로운 acess token을 사용자에게 발급해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[넷플릭스 디자인으로 포트폴리오 만들기]]></title>
            <link>https://velog.io/@ddonghyeo_/%EB%84%B7%ED%94%8C%EB%A6%AD%EC%8A%A4-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9C%BC%EB%A1%9C-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/%EB%84%B7%ED%94%8C%EB%A6%AD%EC%8A%A4-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9C%BC%EB%A1%9C-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 07 Mar 2023 14:49:01 GMT</pubDate>
            <description><![CDATA[<p>포트폴리오를 제작해야 할 일이 있어서 넷플릭스 디자인으로 만들어 보았다.
0부터 100까지 모두 자체로 제작했다.😬
하루만에 만든거라서 퀄리티가 조금 떨어질 수도 있다...</p>
<blockquote>
<h3 id="시작-페이지">시작 페이지</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/42d78c5d-cddb-4ae9-b63c-1e3605006f2a/image.png" alt=""></p>
<p><strong>body</strong></p>
<pre><code class="language-html">&lt;body&gt;
    &lt;div class=&quot;main&quot;&gt;
        &lt;div class=&quot;label&quot;&gt;포트폴리오를 시청할 프로필을 선택하세요.&lt;/div&gt;
        &lt;div class=&quot;content&quot;&gt;
            &lt;div class=&quot;profile&quot;&gt;
                &lt;div class=&quot;link&quot; onclick=&quot;location.href = &#39;/core.html&#39;&quot;&gt;
                    &lt;div class=&quot;icon&quot; style=&quot;background-image:url(http://occ-0-3077-988.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABUlYJLV2Ertu0sYZd9-1UV4wjH5MyxqJQGgHG1hojPGBK8BUitjiZjF7EO8XMzRytv5MWGz3sC7yyTxhDQZhp_Sfw5L4XOvwL3k7.png?r=832)&quot; &gt;&lt;/div&gt;
                    &lt;div class=&quot;name&quot;&gt;Jade Rabbit&lt;/div&gt;
                &lt;/div&gt;
                &lt;div class=&quot;link&quot; onclick=&quot;location.href = &#39;/core.html&#39;&quot;&gt;
                    &lt;div class=&quot;icon&quot; style=&quot;background-image:url(http://occ-0-3077-988.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABTqCB8uh6vUUpjPnmHk3iGyky27lLiL16NEFLBfZ4Kdaf9n0lOJFHM72muckX62W7XgI7MGhWwu9ki-vHV_hUJ2odJOr1CN1A_JI.png?r=962)&quot;&gt;&lt;/div&gt;
                    &lt;div class=&quot;name&quot;&gt;Boss Baby&lt;/div&gt;
                &lt;/div&gt;
                &lt;div class=&quot;link&quot; onclick=&quot;location.href = &#39;/core.html&#39;&quot;&gt;
                    &lt;div class=&quot;icon&quot; style=&quot;background-image:url(http://occ-0-3077-988.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABYc--KGyEVdImJyc2oO2nX1ZxWd1A0OnLyD7jC4MbArQ-wSwPmlCI5SqJAXvlcrwJQzl2I5DwwjIcEutazXr9owgSDZnTR9fWTsu4BJLwKdwHovv-IseozG3V_Afdq_0kDMEC_WeWu8c0HWc4kg.png?r=72e)&quot;&gt;&lt;/div&gt;
                    &lt;div class=&quot;name&quot;&gt;Larva Red&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;
</code></pre>
<p><strong>css</strong></p>
<pre><code class="language-css">.main{
    bottom: 0;
    display: flex;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
    z-index: 100;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    background-color: #141414;
    animation: fadein 1s;
    -moz-animation: fadein 1s; /* Firefox */
    -webkit-animation: fadein 1s; /* Safari and Chrome */
    -o-animation: fadein 1s; /* Opera */
}

.content{
    width: auto;
    height: 200px;
    max-width: 80%;
    color: #fff;
    font-size: 3.5vw;
    font-weight: unset;
    width: 100%;
    opacity: 1;
    transition: opacity .4s ease-out;
    margin: 0.67em 0;
    font-size: 2em;
    margin-block-start: 0.67em;
    margin-block-end: 0.67em;
    margin-inline-start: 0px;
    margin-inline-end: 0px;
    text-align: center;
    line-height: 1.4;
    justify-content: space-between;
}

.profile{
    display: flex;
    flex-direction: row;
    justify-content: center;
}

.label{
    font-family: Netflix Sans,Helvetica Neue,Segoe UI,Roboto,Ubuntu,sans-serif;
    color: #fff;
    font-size: 40px;
    font-weight: unset;
    width: 100%;
    opacity: 1;
    transition: opacity .4s ease-out;
    margin: 0.67em 0;
}

.link{
    background: none;
    background-color: none;
    display: block;
    width: 110px;
    height: 150px;
    justify-content: center;
    align-items: center;
    align-content: center;
    justify-items: center;
    text-align: center;
    margin: 20px;
    cursor: pointer;
    border: 1px white;
}

.icon:hover{
    border: 4px solid white;
    border-color: white;
    border-radius: 10px;
}

.icon{
    width: 100px;
    height: 100px;
    border: 0.3em solid transparent;
    border-radius: 4px;
    bottom: 0;
    display: block;
    background-repeat: no-repeat;
    background-size: cover;
    border: 0;
    border-radius: 10px;
    margin: 0 auto;
    transition: border .2s;
}


.name{
    color: grey;
    display: block;
    font-size: 20px;
    line-height: 1.2em;
    margin: 0.6em 0;
    min-height: 1.8em;
    overflow: hidden;
    text-align: center;
    text-overflow: ellipsis;
}

@keyframes fadein {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
@-moz-keyframes fadein { /* Firefox */
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
@-webkit-keyframes fadein { /* Safari and Chrome */
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
@-o-keyframes fadein { /* Opera */
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}</code></pre>
<p>넷플릭스를 시작하면 프로필을 선택하는 화면이다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/f4f43de2-a6e0-4b6b-81db-259c755bae97/image.gif" alt=""></p>
<p>hover 속성을 이용하여 선택할 때 border속성을 추가했다.</p>
<blockquote>
<h3 id="메인-페이지">메인 페이지</h3>
</blockquote>
<p>프로필을 클릭하면 나타나는 메인 페이지이다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/b7a11f65-adf3-4b8d-9faa-2c77ec5bda17/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/3b3c0016-26ad-4ecb-ba6b-7e514bde3d7f/image.gif" alt=""></p>
<p>마찬가지로 hover 속성을 이용하여 애니메이션 효과를 주었다.</p>
<pre><code class="language-css">.core_cont img{
    background-color: white;
    width: 200px;
    height: 120px;
    margin-right: 5px;
    transition: all .5s;
}

.core_cont img:hover{
    transform: scale(1.4);
}
</code></pre>
<p>transition 속성을 이용하면 부드러운 효과를 줄 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/d435fcdd-97b6-41ab-951e-a53108d53eb8/image.gif" alt="">
프로젝트를 선택하면 클릭 시 모달창을 띄워서 상세 정보를 기술했다.</p>
<h3 id="모달-창">모달 창</h3>
<h4 id="html">html</h4>
<pre><code class="language-html">&lt;!-- modal --&gt;
    &lt;div id=&quot;waither&quot; class=&quot;modal&quot;&gt;
        &lt;div class=&quot;modal_content&quot;&gt;
            &lt;span class=&quot;close&quot; onclick=&quot;modal_close(this)&quot; close=&quot;waither&quot;&gt;&amp;times;&lt;/span&gt;
            &lt;div class=&quot;modal_div&quot;&gt;
                &lt;div class=&quot;modal_title&quot; id=&quot;waither_title&quot;&gt;&lt;/div&gt;
                &lt;div class=&quot;modal_desc&quot;&gt;
                    &lt;div class=&quot;desc_name&quot;&gt;제목&lt;/div&gt;
                    &lt;div class=&quot;desc_inf&quot;&gt;
                        - 설명
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;</code></pre>
<h4 id="css">css</h4>
<pre><code class="language-css">.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 3; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    overflow: hidden;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background-color: rgb(0,0,0); /* Fallback color */
    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
    -moz-animation: fadein 1s; /* Firefox */
    -webkit-animation: fadein 1s; /* Safari and Chrome */
    -o-animation: fadein 1s; /* Opera */
}

.modal_content{
    overflow: hidden;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background: linear-gradient(180deg,#141414,black);;
    line-height: 10px;
    text-align: center;
    background-color: #fefefe;
    border-radius: 5px;
    width: 60%;
    max-width: 900px;
    height: auto;
    margin:0;
    position:fixed;
    top:50%;
    left: 50%;
    transform:translate(-50%, -50%);
    -webkit-transform:translate(-50%, -50%);
    -ms-transform:translate(-50%, -50%);
}

.close {
    display: flex;
    margin: 20px;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 50px;
    background-color: #141414;
    color: #fff;
    float: right;
    font-size: 2rem;
    font-weight: lighter;
    text-align: center;
    cursor: pointer;
    justify-content: center;
    align-items: center;
}

.modal_title{
    width: 100%;
    height: 600px;
    background-size: cover;
    box-shadow: inset 0px -130px 80px #141414;
}

.modal_desc{
    margin: 0;
    background-color: #161819;
    color: white;
    height: 200px;
    text-align: left;
}

.desc_name{
    font-size: 2rem;
    font-weight: 700;
    height: 20px;
    margin-left: 20px;
}

.desc_inf{
    font-size: 1rem;
    font-weight: 400;
    height: auto;
    margin: 20px;
    line-height: 30px;
}</code></pre>
<p>실제 넷플릭스의 X버튼을 비슷하게 만들어 보았다.
항상 비슷한 형식의 포트폴리오를 보고 와서 그런지, 새롭게 시도해보았다.</p>
<p>완성된 페이지는 netlify를 통해서 정적 파일 업로드로 배포했다.
<a href="https://donghyunportfolio.netlify.app/">https://donghyunportfolio.netlify.app/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Jwt를 이용하여 Api서버 개인정보 암호화하기]]></title>
            <link>https://velog.io/@ddonghyeo_/Jwt%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Api%EC%84%9C%EB%B2%84-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4-%EC%95%94%ED%98%B8%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/Jwt%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Api%EC%84%9C%EB%B2%84-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4-%EC%95%94%ED%98%B8%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 04 Mar 2023 13:53:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/3390cb83-cc10-4ab3-a7f5-a4b7ff9ac78f/image.png" alt=""></p>
<p>서버의 HTTPS설정은 서버에서 데이터 전송 과정에 누군가 데이터를 가로채 볼 수 없도록 보호하는 수단이다.</p>
<p>하지만 이것은 서버의 통신이 안전한 것이지, 서버가 안전하다는게 아니다.</p>
<p>만약 Client에서 비밀번호와같은 개인정보를 서버에서 전송했는데, 이러한 개인정보를 DB에 저장해두었다면,</p>
<p>그 DB가 노출된다면 개인정보가 노출되는 것이다... 😬</p>
<p>그래서 JWT(JSON Web Token)을 이용하여 데이터를 암호화 할 것이다.</p>
<p>JWT는 Base64 URL 인코딩을 사용하여 토큰의 내요을 암호화하고 서명하여 인증 및 권한부여를 해준다.</p>
<p>JWT는 서명을 통해 서버에서 검증 과정을 거쳐 변조가 되었는지 확인할 수 있으며, 만료 시간을 설정하여 토큰의 악용을 막을 수 있다.</p>
<p>보통 AccessToken과 RefreshToken으로 나뉘는데, 자세한 내용은 다음에 포스팅 하겠다.</p>
<h2 id="토큰-생성">토큰 생성</h2>
<pre><code class="language-java">public class JwtUtil {

    Long exp = 3600000L; // 1시간(3600000밀리초)

    private static final String SECRET_KEY = &quot;SecretKey&quot;; // JWT를 암호화하기 위한 Secret Key

    public static String generateToken(String subject, long ttlMillis) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // JWT 토큰 생성
        String token = Jwts.builder()
                .setSubject(subject) //subject 설정
                .setIssuedAt(now) //토큰이 발급된 시간
                .setExpiration(new Date(nowMillis + exp)) //유효기간 설정
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY) //서명
                .compact(); //합치기
        return token;
    }</code></pre>
<p>exp는 토큰의 만료시간이다. accessToken, refreshToken 등 용도에 따라 다르게 설정해야 한다.
만료시간이 길수록 탈취당할 확률이 높아진다.
따라서 짧은 인증을 요구할 때에는 짧은 만료시간을 두는게 좋다.</p>
<h2 id="토큰-유효성-검증">토큰 유효성 검증</h2>
<pre><code class="language-java">public static boolean validateToken(String token) {
        try {
            // JWT 토큰 파싱
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(KEY)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
            return true;
        } catch (Exception ex) {
            return false;
        }
    }</code></pre>
<p>토큰이 유효한지 검사하는 로직이다. 설정해둔 KEY를 통해 토큰을 파싱하고 유효한 토큰이면 true, 유효하지 않으면 false를 반환한다.</p>
<h2 id="토큰-body값-얻기">토큰 Body값 얻기</h2>
<pre><code class="language-java">public static Claims getJwtBody(String token) {
        // JWT 토큰 파싱
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(KEY)
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claims;
    }</code></pre>
<p>토큰 내에 Body값을 가져오려면, SignKey를 통해 파싱하고 getBody()를 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[뉴모피즘 스타일로 졸업요건 검사 사이트 제작하기]]></title>
            <link>https://velog.io/@ddonghyeo_/%EB%89%B4%EB%AA%A8%ED%94%BC%EC%A6%98-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A1%9C-%EC%A1%B8%EC%97%85%EC%9A%94%EA%B1%B4-%EA%B2%80%EC%82%AC-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ddonghyeo_/%EB%89%B4%EB%AA%A8%ED%94%BC%EC%A6%98-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A1%9C-%EC%A1%B8%EC%97%85%EC%9A%94%EA%B1%B4-%EA%B2%80%EC%82%AC-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 02 Mar 2023 15:26:45 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/a0146f5e-8ad5-450f-8e95-06ceefe6e7b8/image.png" alt="">
나는 <strong>주변의 문제들</strong>을 해결하는 방법을 고안하고, 기능을 구현해서 배포하는 것을 꿈꾸어 왔고, 이번 졸업요건 사이트를 만들면서 그 꿈을 실현해 보았다.</p>
<blockquote>
<h3 id="계기">계기</h3>
</blockquote>
<p>동기 형 중에 졸업을 앞두고 있는 형이 있었다. 나도 마찬가지로, 수강신청을 할 때 즈음이었다.
3학년을 앞두고 있었기 때문에, 졸업요건을 따져가면서 어떤 과목을 수강해야 하는지 꼼꼼히 따져보며 장바구니 에 담을 과목을 고민하고 있었다.</p>
<p>주변의 문제들을 눈에 불을 켜고 찾고 있었던 때라, 개발하기 너무 좋은 타겟이라고 생각했다.</p>
<blockquote>
<h3 id="벤치마킹">벤치마킹</h3>
</blockquote>
<p>웹 사이트를 제작하기 전에, 먼저 만들어져 있는 사이트를 둘러봤다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/d76187c1-ad41-4e58-adec-ca9a802f5ca3/image.png" alt="">
세종대학교의 졸업요건 검사 사이트인 Please Graduate.
사이트가 정말 잘 되어있다고 생각해서 벤치마킹하고 웹사이트를 제작했다.
후에는 사이트 개발자와 컨택이 되어 소개도 했다.</p>
<blockquote>
<h3 id="역할">역할</h3>
</blockquote>
<p>사실 나는 백엔드 개발자를 꿈꾸고 처음부터 백엔드 작업을 하고 싶었다.
하지만 팀원들을 모으다 보니, 예상 외로 <strong><em>프론트</em></strong> 자리가 부족했다.
마침 나는 웹사이트 프론트 부분을 한 번도 해보지 않았기 때문에, 백엔드 개발자여도 필요한 지식분야라고 생각했다. 상황도 이러한지라 프론트 작업을 하기로 했다.</p>
<blockquote>
<h3 id="프론트-공부">프론트 공부</h3>
</blockquote>
<p>프론트에 대한 아주 아주 기초만 알고 있었기 때문에, 더 깊은 공부가 필요했다.
기초 개념은 TCP School을 이용했다.
<a href="http://www.tcpschool.com/html/intro">http://www.tcpschool.com/html/intro</a>
이외에, 마침 인프런에서 css강의가 세일하길래 구입해서 들어보았다.
그리고 역시 필요한 부분이 생길 때 마다 구글링을 통해 해답을 얻었다.</p>
<blockquote>
<h3 id="뉴모피즘-스타일">뉴모피즘 스타일</h3>
</blockquote>
<p>나는 사이트의 중요한 부분인 검사 결과 페이지, 교양 추천 페이지를 맡았다.
<del>교양 추천 페이지는 만들다가 보류해두었다.</del></p>
<p>마침 진행하던 프로젝트에 디자이너분이 계셔서 조언을 구하며 뉴모피즘 스타일로 결과 페이지를 구성해 보았다.</p>
<h4 id="박스-css">박스 css</h4>
<pre><code class="language-css">.resultbox {
    display: flex;
    flex-direction: column;
    width: 600px;
    height: 300px;
    margin: 45px 35px 20px;
    border-style: solid;
    border-width: 1px;
    border-radius: 20px;
    background: linear-gradient(125deg,#E8EBF2,#F2F3F7);
    border: 1px solid white;
    box-shadow: 10px 15px 30px #dddedf;
    perspective-origin: 50% 50%;
    perspective: 400px;

    /* fade */
    display: inline-block;
    padding: 10px;
    animation: fadein 2s;
    -moz-animation: fadein 2s; /* Firefox */
    -webkit-animation: fadein 2s; /* Safari and Chrome */
    -o-animation: fadein 2s; /* Opera */
}</code></pre>
<p>먼저 항목들을 세로로 나열하고, 원 그래프를 통해 얼마나 완료되었는지 직관적으로 확인할 수 있게 배치했다.</p>
<pre><code class="language-css">display: flex;
flex-direction: column;</code></pre>
<p>두 속성을 사용하여 세로로 배치했고, 내부 박스의 크기를 지정하여 왼쪽에서 오른쪽으로 정렬되게 만들었다.</p>
<pre><code class="language-css">border-style: solid;
border-width: 1px;
border-radius: 20px;
background: linear-gradient(125deg,#E8EBF2,#F2F3F7);
border: 1px solid white;
box-shadow: 10px 15px 30px #dddedf;</code></pre>
<p>뉴모피즘 스타일을 적용시킨 부분이다. 은은하게 떠있는 효과를 주기 위해 box-shadow의 위치와 효과를 적절하게 조합해야 한다.
추가로, 사이트에 들어왔을 때 fade효과를 넣어주었다.
<img src="https://velog.velcdn.com/images/ddonghyeo_/post/91615f25-5e85-4157-9bd9-694497710f07/image.png" alt="">
아이콘은 fontawesome의 무료 아이콘을 이용했다.</p>
<h4 id="원-그래프">원 그래프</h4>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/287ad750-c539-4d94-a72a-28d0ca082ca2/image.png" alt=""></p>
<h4 id="css">css</h4>
<pre><code class="language-css">/* piechart */
.pie-chart {
    position: relative;
    background-color: #E8EBF2;
    /* display:inline-block; */
    width: 120px;
    height: 120px;
    border-radius: 50%;
    transition: 0.3s;
    margin-left: 15px;
    box-shadow: 1px 1px 6px 4px #cacacc inset;
    display:flex;
    align-items: center;
    justify-content: center;
  }
  .pie-chart-color{
    width: 80%;
    height: 80%;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .pie-chart-inner{
    background-color: #E8EBF2;
    width: 83%;
    height: 83%;
    border-radius: 50%;
  }

  span.center{
    background: #E8E9ED;
    /* display : block; */
    position: absolute;
    top:50%; left:50%;
    width:50px; height:50px;
    border-radius: 50%;
    text-align:center; 
    line-height: 50px;
    font-size:13px;
    font-weight: bold;
    color: #0066FF;
    font-weight: bold;
     transform: translate(-50%, -50%);
     box-shadow: 2px 2px 10px 4px #bebec0;
  }</code></pre>
<h4 id="js">js</h4>
<pre><code class="language-js">//pie-chart
function draw(max, classname, colorname){
  var i=1;
   var func1 = setInterval(function(){
     if(i&lt;max){
         color1(i,classname,colorname);
         i++;
     } else{
       clearInterval(func1);
     }
   },20);
}

function color1(i, classname,colorname){
  $(classname).css({
    &quot;background&quot;:&quot;conic-gradient(rgba(0, 102, 255, 0.1) 0%, rgba(0, 102, 255, 1) &quot;+i+&quot;%, rgb(232, 235, 242) &quot;+i+&quot;%, rgb(232, 235, 242) 100%)&quot;
  });
}</code></pre>
<p>원 그래프는 div 3개를 이용하여 만들었다. 뉴모피즘 스타일을 지키며 box-shadow를 넣어주고, 원 중간에 퍼센트가 나타나게 제작했다.
추가로 js Interval을 이용하여 그래프가 자연스럽게 차오르는 효과를 연출했다.</p>
<h4 id="카운트-애니메이션">카운트 애니메이션</h4>
<pre><code class="language-js">$(&#39;.count&#39;).each(function() { 
    var $this = $(this),
    countTo = $this.attr(&#39;data-count&#39;);

    $({ countNum: $this.text()}).animate({
        countNum: countTo 
      },
    {
    duration: 1500, // 애니메이션이 완료될때까지 걸리는 시간
    easing:&#39;linear&#39;, // 애니메이션 효과 방식
    step: function() { // 움직임 각 스텝별로 실행될 함수
    $this.text(Math.floor(this.countNum));
    },
    complete: function() { // 움직임이 멈춘 후 실행될 함수
    $this.text(this.countNum);
    // this.countNum이 $this의 text값이 된다
    }
  });  
});</code></pre>
<p>카운트 애니메이션을 통해, 숫자가 자연스럽게 올라가는 효과를 연출했다.</p>
<p>완성된 페이지</p>
<p><img src="https://velog.velcdn.com/images/ddonghyeo_/post/fcb04996-32eb-4ae6-911d-aec0cd6a245b/image.gif" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>