<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wony_yoon.log</title>
        <link>https://velog.io/</link>
        <description>무럭무럭 성장중🌿</description>
        <lastBuildDate>Fri, 18 Apr 2025 06:20:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>wony_yoon.log</title>
            <url>https://velog.velcdn.com/images/wony_yoon/profile/5a845c1d-792a-4130-97e7-6b7770afa6c5/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. wony_yoon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wony_yoon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[apache 그리고 fpm-php]]></title>
            <link>https://velog.io/@wony_yoon/apache-%EA%B7%B8%EB%A6%AC%EA%B3%A0-fpm-php</link>
            <guid>https://velog.io/@wony_yoon/apache-%EA%B7%B8%EB%A6%AC%EA%B3%A0-fpm-php</guid>
            <pubDate>Fri, 18 Apr 2025 06:20:56 GMT</pubDate>
            <description><![CDATA[<p>워드프레스로 만든 우리의 홈페이지가 어느날 죽었다.
다급하게 인스턴스를 재부팅해서 살려냈지만 몇개월 뒤 또 죽어버렸다.
계속 죽으니 뭔가 원인이 있겠다 싶어 한번 파보았다</p>
<p>기존 우리의 ec2사양은</p>
<ul>
<li>t3.small</li>
<li>vCPU 2개</li>
<li>메모리 2GB</li>
<li>네트워크 최대 5Gbps</li>
<li>스왑은 2GB로 설정해둔 상태였다</li>
</ul>
<p>jmeter로 150명의 유저가 1분동안 2번씩 접속하도록 셋팅해서 돌려보니
<img src="https://velog.velcdn.com/images/wony_yoon/post/eae063d9-2fe0-4540-b777-f44c1301c5e5/image.png" alt=""></p>
<p>인스턴스가 중지되지는 않았지만 어마어마하게 느려지며 응답을 받지 못하게 되었다
원인을 찾다가 <strong>fpm-php</strong>라는것을 알게되었다</p>
<p>기존우리의 워드프레스 홈페이지는 <strong>mod_php</strong>로 되어 있었다
이 둘의 차이를 음식점으로 비유해보자면</p>
<hr>
<p>mod_php는 서버가 문도 열고 손님 응대도 하고 요리도 해야하는 것이다 (손님 한명당 직원 한명 배정)
그래서 직원의 처리 속도가 느리고 부하가 온다
<img src="https://velog.velcdn.com/images/wony_yoon/post/faf4d7c0-e053-4893-a804-b1c413ef5570/image.png" alt=""></p>
<hr>
<p>fpm-php는 서버는 문만 열고 요리는 전문 요리사가 병렬로 요리를 하는 방식으로 효율성이 증대된 방식이다
<img src="https://velog.velcdn.com/images/wony_yoon/post/01b485dd-aa28-4d01-a533-ae9f62dca6a8/image.png" alt=""></p>
<p>ssh에서 아래의 명령어를 입력하면 위처럼 볼 수 있다</p>
<pre><code class="language-bash">top</code></pre>
<p>mod_php는 apache2 내부에서 php를 처리하는 방식이고
php-fpm은 apache2와 별개로 php를 처리하는 방식이다
php-fpm은 apache와 php처리가 분리되어 php만 병렬로 처리해 속도가 훨씬 빠르다</p>
<h2 id="max_children">max_children</h2>
<p>사진에서 보듯이 mod_php에서는 수많은 프로세스가 생성된 것을 볼 수 있다
우리는 150개가 max이다</p>
<p>fpm-php는 20개만 생성된 것을 볼 수 있다
max_children을 20으로 설정해두었기 때문이다</p>
<p>max_children = 20 이 의미하는 바는</p>
<p>동시에 php를 처리할 수 있는 프로세스 수가 20개
예를 들어 php-fpm프로세스가 20개 있다면 (pm.max_children = 20)
php 요청을 처리하는데 0.2초가 걸린다고 가정 
그렇다면 1초에 한 프로세스가 5명 처리 20*5 = 1초에 100명 가능
(실제 처리 시간은 응답시간, db, 브라우저등 여러 요소에 따라 처리 시간이 상이함)</p>
<p>20으로 설정한 이유는 </p>
<p>max_children = (Total RAM - OS RAM - DB RAM) / average PHP process memory</p>
<ul>
<li>일반적인 워드프레스 기준: 25~35MB</li>
<li>무거운 테마/플러그인: 40~50MB도 가능</li>
<li>총 메모리: 2GB = 2048MB</li>
<li>OS + 기타 프로세스 약 500MB 사용</li>
<li>MySQL 등 백엔드 사용 약 500MB</li>
<li>PHP-FPM에 쓸 수 있는 여유 메모리: 약 1000MB</li>
</ul>
<p>평균 PHP 프로세스 메모리 40MB라 가정 시: max_children = 1000 / 40 = 25</p>
<p>25는 cpu에 무리를 줄 수 있어 20으로 안정적으로 유지하기 위해 설정하였다</p>
<h2 id="테스트-결과">테스트 결과</h2>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/19162186-5442-4098-8165-7b91a422038a/image.png" alt="">
개선전에는 150명이 1분간 2번씩 방문 했을때 
10분이상이 소요되며 14분이 지났을때 요청을 중지시켜버렸다
상당한 시간이 소요된다는걸 볼 수 있었다
cpu도 49%까지 올라갔다</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/54025cef-f177-46ce-b6f9-30f8e0c1dfa3/image.png" alt=""></p>
<p>개선후 동일한 환경에서 테스트를 진행하였더니
1분 54초만에 모든 유저가 테스트를 완료하였다
에러율도 0%였고 cpu도 22%까지 밖에 가지 않았다</p>
<p>매우 큰 차이를 보였다</p>
<h2 id="결론">결론</h2>
<p>워드프레스처럼 플러그인을 사용하고 용량이 큰 프로젝트의 경우 fpm으로 php를 실행하는것이 빠르며
메모리 누수도 줄일 수 있다
앞으로 더 개선 할 수 있는 방법은 캐시등을 도입하고 mysql의 쿼리가 오래걸리는 부분이 있는지 확인하는것이 좋게따</p>
<ol>
<li><p>mod_php 비활성화</p>
<pre><code class="language-bash">sudo a2dismod php8.1</code></pre>
</li>
<li><p>prefork MPM 비활성화</p>
<pre><code class="language-bash">sudo a2dismod mpm_prefork</code></pre>
</li>
<li><p>event MPM 활성화</p>
<pre><code class="language-bash">sudo a2enmod mpm_event</code></pre>
</li>
<li><p>php-fpm 사용을 위한 설정 (proxy 관련)</p>
<pre><code class="language-bash">sudo a2enmod proxy_fcgi setenvif</code></pre>
</li>
<li><p>php-fpm 모듈 활성화</p>
<pre><code class="language-bash">sudo a2enconf php8.1-fpm</code></pre>
<p>이 설정은 /etc/apache2/conf-available/php8.1-fpm.conf 를 Apache에 연결</p>
</li>
<li><p>php-fpm 서비스 실행 (이미 설치돼 있으면 생략 가능)</p>
<pre><code class="language-bash">sudo systemctl restart php8.1-fpm</code></pre>
</li>
<li><p>Apache 재시작</p>
<pre><code class="language-bash">sudo systemctl restart apache2</code></pre>
<p>확인 명령어</p>
<pre><code class="language-bash">apache2ctl -M | grep mpm
결과: mpm_event_module (shared)
</code></pre>
</li>
</ol>
<p>ps aux | grep php-fpm
결과: 결과값에 php-fpm이 있어야함</p>
<pre><code>![](https://velog.velcdn.com/images/wony_yoon/post/fa3bd20a-bd82-4858-80b5-dfdcd27c39fc/image.png)

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[ECS 도커로 배포하기]]></title>
            <link>https://velog.io/@wony_yoon/ECS-%EB%8F%84%EC%BB%A4%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/ECS-%EB%8F%84%EC%BB%A4%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 11 Sep 2024 07:11:56 GMT</pubDate>
            <description><![CDATA[<p>요즘 제일 많이 사용하고있는 <strong>ECS에 도커로 배포</strong> 하기에 대해 내가 까먹지 않기 위해 그리고 까먹어도 다시 볼 수 있도록하기 위해 글을 오랜만에 씁니당</p>
<hr>
<h2 id="1-프라이빗-리포지토리-생성✏️">1. 프라이빗 리포지토리 생성✏️</h2>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/65dfb7c7-607f-4c6e-a66c-66a7d6b95db2/image.png" alt="">
먼저 ECS에 들어가서 클러스터를 생성하고 프라이빗 리포지토리를 하나 생성해줍니다
저는 이미 생성된 클러스터가 있어서 <strong>프라이빗 리포지토리</strong>만 생성했습니다</p>
<hr>
<h2 id="2-푸시명령을-이용하여-도커로-빌드하여-업로드✏️">2. 푸시명령을 이용하여 도커로 빌드하여 업로드✏️</h2>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/745956da-c304-439e-808f-1c80e04faec2/image.png" alt="">
리포지토리가 생성되면 푸시명령을 확인할 수 있는데 이 명령어를 가지고 프로젝트로 이동해서 Dockerfile이 있는 곳에서 위의 명령어를 실행하면 리포지토리로 올릴 수 있습니다</p>
<pre><code class="language-docker"># Step 1: Build the Next.js application
FROM --platform=$BUILDPLATFORM node:18-alpine AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm install

COPY . .
RUN npm run build

# Step 2: Run the Next.js application
FROM --platform=linux/amd64 node:18-alpine AS runner

WORKDIR /app

ENV NODE_ENV production

COPY --from=builder /app/package.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public

EXPOSE 80

CMD [&quot;npm&quot;, &quot;start&quot;]</code></pre>
<p>저의 도커파일 인데요
Next.js 애플리케이션을 빌드하고 실행하기 위한 두 단계 빌드 프로세스를 정의하고 있습니다</p>
<h4 id="🍯여기서-꿀팁🍯">🍯여기서 꿀팁🍯</h4>
<p>푸시 명령어를 매번 aws에 들어와서 확인하고 매번 복붙해서 실행하기보다는
프로젝트에 <strong>.sh 파일</strong> 하나를 만들어서 푸시명령어를 순서대로 입력해두고
.sh 파일을 실행하면 알아서 순차적으로 푸시명령어를 실행합니다
저는 deploy.sh라고 파일을 만들어 두었는데요
<img src="https://velog.velcdn.com/images/wony_yoon/post/264ccc43-b374-44da-b641-8541e8ac327c/image.png" alt=""></p>
<p>이 파일을 실행하기 위해서는 아래의 명령어를 통해 권한을 부여해주어야 사용이 가능합니다</p>
<pre><code>chmod +x deploy.sh</code></pre><p>사용할때는 아래의 명령어만 입력하면 됩니다</p>
<pre><code>./deploy.sh</code></pre><p>이 파일을 만들어두지 않았으면 푸시명령어를 하나하나 실행했어야했는데 한번의 실행만으로 이렇게 바로 빌드가 되니까 정말 편하네요👍</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/f796ad1c-39e4-41ef-8549-368e37d35f90/image.png" alt=""></p>
<p>업로드가 성공적으로 완료되면 리포지토리에 이미지가 추가됩니다</p>
<hr>
<h2 id="3-새-태스크-정의-생성✏️">3. 새 태스크 정의 생성✏️</h2>
<p>ECS에 가면 좌측 메뉴에 <strong>태스크정의</strong>가 있을텐데요 들어가서 <strong>새 태스크 정의 생성</strong>버튼을 찾을 수 있습니다</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/f14ad554-2019-44b3-9d86-d68b5f71231b/image.png" alt="">
먼저 태스크의 이름을 입력하고
<img src="https://velog.velcdn.com/images/wony_yoon/post/cf72bc03-a14d-434f-bcdf-69acf46ad26e/image.png" alt="">
컨테이너 세부 정보를 입력합니다 
<strong>컨테이너 이름</strong>을 적고, <strong>이미지URI</strong>를 입력해야하는데 여기에 적는 이미지URI는 아까 생성한** 리포지토리의 이미지 URI** 를 적습니다
그 외 다른 옵션들은 기본값을 유지하였습니다</p>
<hr>
<h2 id="4-클러스터의-서비스-생성✏️">4. 클러스터의 서비스 생성✏️</h2>
<p>ECS의 클러스터 메뉴에 진입하면 하단에 서비스탭이 있습니다
여기서 생성 버튼을 통새 <strong>서비스를 생성</strong>합니다
<img src="https://velog.velcdn.com/images/wony_yoon/post/be6a665e-b882-442f-a3b6-26afee352c1b/image.png" alt="">
패밀리는 3번에서 <strong>정의한 task를 선택</strong>하고
서비스의 이름은 직접 생성해주시면 됩니다
<img src="https://velog.velcdn.com/images/wony_yoon/post/faadf316-0026-4208-9354-78e0cc0a8950/image.png" alt="">
네트워킹 탭에서는 <strong>VPC와 서브넷, 보안그룹</strong>을 설정하여야하는데 저는 보안그룹만 저희가 사용하는 그룹으로 설정했습니다
<img src="https://velog.velcdn.com/images/wony_yoon/post/4949fdfa-0356-4526-ad3f-76921a3dfa6f/image.png" alt="">
마지막으로 <strong>로드밸런서</strong>를 설정해줍니다
유형은 <strong>Application Load Balancer</strong>
이고 컨테이너는 3번 태스크 정의 할때 생성했던 <strong>컨테이너</strong>를 선택해줍니다
<strong>새 로드밸런서를 생성</strong>할거니까 이름도 지어주고
<strong>대상그룹이름</strong>도 지어줍니다
상태 확인 경로는 프로젝트에서 접근이 가능한 페이지 url을 하나 입력해줍니다</p>
<p>서비스 생성이 완료되면 리포지토리에 올렸던 이미지를 서비스에 배포하게됩니다
이때 배포하는데에 시간이 조금 걸리는데 기다리면서 배포가 성공하는지 확인합니다</p>
<p>기다리는동안 EC2의 좌측메뉴 중 로드밸런서탭으로 이동하면 
<img src="https://velog.velcdn.com/images/wony_yoon/post/c0d13afe-4a7a-4345-a164-b646c1665973/image.png" alt="">
로드밸런서가 잘 만들어진것을 확인할 수 있습니다</p>
<hr>
<h2 id="5-https-적용하기✏️">5. https 적용하기✏️</h2>
<p>위 사진은 현재 80포트만 열려있어서 http의 접근만 가능한 상황입니다
<strong>443포트를 열어주어 https의 접근도 허용</strong>해볼게요
위 사진에서 <strong>리스너 추가</strong> 버튼을 눌러 리스너를 추가합니다
<img src="https://velog.velcdn.com/images/wony_yoon/post/eeb2dcaa-beae-4ad7-bd81-61d9bdaab94e/image.png" alt="">
<strong>HTTPS 프로토콜에 443포트</strong>를 설정하고 
대상그룹은 4번 로드밸런서 설정시 입력한 대상그룹을 선택해줍니다
<img src="https://velog.velcdn.com/images/wony_yoon/post/33122ca0-706c-450d-b104-9a7a79b8eb13/image.png" alt="">
아래로 내려 보안 리스너도 설정해줍니다
인증서 소스는 저는 ACM에 있는 인증서를 통해 <strong>ssl 인증</strong>을 해주었습니다<img src="https://velog.velcdn.com/images/wony_yoon/post/fa6f0065-f071-49bd-a0e0-d5cab5a86331/image.png" alt="">
등록을 마치면 443포트도 잘 열려있네요</p>
<hr>
<h2 id="5-도메인-설정하기✏️">5. 도메인 설정하기✏️</h2>
<p>지금까지는 제대로된 도메인 없이 ip주소로만 접근이 가능했는데 도메인을 부여해보겠습니다
먼저 <strong>Route53</strong>으로 이동해 호스팅영역으로 갑니다
저는 이미 원하는 도메인을 등록해놨기에 도메인을 선택하고 <strong>레코드 생성</strong>을 선택합니다
<img src="https://velog.velcdn.com/images/wony_yoon/post/2a863d45-83f4-4a5a-8e65-59866c4698d8/image.png" alt="">
레코드 이름을 입력하고
트래픽 라우팅대상을 <strong>Application/Classic Load Balancer</strong>에 대한 별칭으로 선택합니다
region은 서울로 선택하고
로드밸런서는 4번에서 생성한 로드밸런서를 선택하면 원하는 도메인으로 접근이 가능합니다</p>
<hr>
<p>이렇게 저의 ECS에 도커로 빌드하기 글이 마무리되었습니다
사실 제가보기 위한 용도라 다른 분들이 보기에는 많이 미흡한 점도 있으리라 생각되지만 
이 글을 누군가 읽고 많은 도움이 되면 좋겠습니다🙏
감사합니다😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SPF, DKIM, DMARC 메일 보안 설정하기]]></title>
            <link>https://velog.io/@wony_yoon/SPF-DKIM-DMARC-%EB%A9%94%EC%9D%BC-%EB%B3%B4%EC%95%88-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/SPF-DKIM-DMARC-%EB%A9%94%EC%9D%BC-%EB%B3%B4%EC%95%88-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 22 Mar 2024 03:05:02 GMT</pubDate>
            <description><![CDATA[<p>내가 보낸 메일이 스팸 메일함으로 가있다면 마케팅적으로 손해를 볼 것이다 </p>
<p>이 메일이 스팸이 아니라는걸 증명하는 보안정책을 설정해주어야 하는데 그 방법으로는
SPF, DKIM, DMARC가 있다</p>
<p>그 설정 방법을 적어보려고 한다</p>
<blockquote>
<p>우리의 도메인은 aws로 관리되고 있고 메일은 naverworks를 이용해 우리의 도메인주소로 메일을 보내고 있다 </p>
</blockquote>
<h1 id="1-spf">1. SPF</h1>
<p>aws에서 route53에서 레코드를 생성해주어야 한다
<img src="https://velog.velcdn.com/images/wony_yoon/post/640ef222-f217-4b17-bb14-39eda0d98cba/image.png" alt=""></p>
<p>레코드이름은 빈칸으로 두어도 되고 
레코드 유형은 <strong><em>TXT</em></strong> 로 설정한다
레코드 값은 <strong>*&quot;v=spf1 include:worksmobile.com -all&quot;*</strong>
여기에서 <strong><em>worksmobile.com</em></strong> 은 naverworks를 사용했기 때문에 이렇게 적어준것이다
필요에따라 값을 변경해서 쓰면 될것 같다</p>
<p>레코드를 생성한다</p>
<h1 id="2-dkim">2. DKIM</h1>
<p>aws에서 Amazon SES에서 자격증명탭으로 이동하면 인증 부분에 DomainKeys Identified Mail(DKIM) 부분이 있다 
없다면 키를 생성한다 나의 경우는 이미 key가 생성이 되어 있었다 </p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/51bca1d8-5071-4a8a-963a-06e59ad34d9c/image.png" alt="">
다시 route53으로 돌아가 레코드를 생성해준다 
<img src="https://velog.velcdn.com/images/wony_yoon/post/2a0e5fcd-8e01-423a-97af-4de0465a6487/image.png" alt="">
레코드 이름은 DKIM에서 DNS레코드의 이름을 적는다 
레코드 유형은 <strong><em>CNAME</em></strong> 으로 설정한다
레코드 값은 DKIM에서 DNS레코드의 값을 적는다</p>
<p>3개의 key로 레코드 3개를 모두 생성한다</p>
<h1 id="3-dmarc">3. DMARC</h1>
<p>aws route53에서 레코드를 하나 생성한다
<img src="https://velog.velcdn.com/images/wony_yoon/post/7d061d1b-cafc-4799-8f22-c213f5c6565f/image.png" alt="">
레코드이름은 <strong><em>_dmarc</em></strong> 으로 설정한다
레코드 유형은 <strong><em>TXT</em></strong> 로 설정한다
레코드 값은 <strong>*&quot;v=DMARC1; p=none; rua=mailto:이메일주소;&quot;*</strong></p>
<p>여기에서 레코드값의 태그가 여러 종류가 있는데 </p>
<h2 id="1-vdmarc1">1. v=DMARC1</h2>
<p>DMARC의 버전을 설명하는 태그이다 이 태그는 <strong><em>필수</em></strong> 항목이며, DMARC1로 설정해야한다. V 태그는 반드시 모든 태그 중 가장 앞에 있어야 한다.</p>
<h2 id="2-pnone-pquarantine-preject">2. p=none, p=quarantine, p=reject</h2>
<p>발신자 주소에 설정된 도메인이 SPF, DKIM 설정이 되어 있지 않은 경우 이 이메일을 어떻게 조치할지에 대한 내용을 이메일 수신 서비스에 전달한다.</p>
<ul>
<li>none: 아무런 조치도 하지 않는다.</li>
<li>quarantine: 이메일을 스팸으로 표시하고 스팸함으로 보낸다.</li>
<li>reject: 이메일의 수신을 거부하며 스팸함에도 도착하지 않는다.</li>
</ul>
<p>이 태그는 v 태그와 같이 필수로 설정해야 하는 항목이다. 
처음에는 none으로 설정해 모니터링을 진행하고 문제가 없다면 quarnatine, reject 순서로 점점 조치를 강화하는 것을 일반적으로 권장하고 있다.</p>
<h2 id="3-pct">3. pct=</h2>
<p>이메일 중 DMARC 정책을 적용할 비율을 정한다. 
값은 1에서 100 사이의 정수로 설정하며 pct 값을 설정하지 않으면 기본값은 100으로 설정되고 발송하는 모든 이메일에 DMARC 정책이 적용된다.</p>
<p>일반적으로 처음에는 pct=5 정도로 낮은 값을 설정해 모니터링을 진행한 뒤 점점 비율을 늘려 최종적으로는 pct=100의 값으로 설정하는 것이 권장된다.</p>
<p>이 태그는 선택사항이다.</p>
<h2 id="4ruamailto">4.rua=mailto:</h2>
<p>DMARC 정책 적용 결과에 대한 보고서를 수신할 이메일 주소를 입력한다.
DMARC 설정이 적용되면 이메일을 받아본 수신 서비스들에서는 정책의 적용 결과를 보고서로 작성해 이메일로 회신한다.
보고서를 분석하면 내 발신자 주소의 이메일이 어떻게 처리되고 있는지 결과를 확인할 수 있습니다.</p>
<p>설정은 이메일 주소에 mailto: 를 포함해서 설정하면 된다.</p>
<p>예) <code>mailto:dmarc-report@example.com</code>
DMARC 보고서를 여러 이메일에서 받아보고 싶다면 각 이메일 주소를 쉼표로 구분하고 개별 주소 앞에 mailto: 를 추가합니다.</p>
<p>예) <code>mailto:dmarc-report-1@example.com, mailto:dmarc-report-2@example.com</code></p>
<p>이 태그는 선택사항이다.</p>
<h2 id="5-sp">5. sp=</h2>
<p>하나의 도메인에 여러 하위 도메인을 사용하는 경우 하위 도메인에서 발송된 이메일에는 상위 도메인과 다른 DMARC 정책을 적용하고 싶은 경우 이 옵션을 사용하면 된다. 
위에서 설정한 p 태그와 같은 역할과 설정 값을 가지지만 sp 태그는 상위 도메인이 아닌 하위 도메인에만 별도로 적용된다는 것에서 차이점이 있다.</p>
<p>none: 아무런 조치를 취하지 않는다.
quarantine: 이메일을 스팸으로 표시하고 스팸함으로 보낸다.
reject: 이메일의 수신을 거부하며 스팸함에도 도착하지 않는다.</p>
<p>이 태그는 선택사항이다.</p>
<h2 id="6-adkim">6. adkim=</h2>
<p>발신자 주소에 설정한 DKIM 설정값이 발송된 이메일에 포함된 DKIM 서명과 어느 정도 일치해야 DMARC 정책을 통과했다고 판단할지 여부를 정의한다.
DKIM 서명은 일반 사용 환경에서는 확인할 수 없고 &#39;이메일 원본&#39;의 헤더 값에서 확인할 수 있다.</p>
<ul>
<li><p>s: DKIM을 설정한 도메인과 이메일을 발신한 도메인이 완전히 일치하는 경우에만 이메일 발송을 허용한다.
예를 들어 example.com 도메인에 DKIM을 설정한 경우 반드시 발신자 도메인은 example.com인 경우에만 발송을 허용한다. 
서브 도메인으로 설정해 발송한 경우에는 두 도메인이 완전히 일치하지 않기 때문에 이메일 발송을 허용하지 않습니다.</p>
</li>
<li><p>r: DKIM을 설정한 도메인과 이메일을 발신한 도메인이 부분적으로 일치하는 경우에도 이메일 발송을 허용한다.
예를 들어 example.com 도메인에 DKIM을 설정한 경우 example.com의 서브 도메인으로 설정된 발신자 이메일 주소도 발송을 허용한다.</p>
</li>
</ul>
<p>기본적으로 r 값으로 설정된다.</p>
<p>이 태그는 선택사항이다.</p>
<h2 id="7-aspf">7. aspf=</h2>
<p>발신자 주소에 설정한 SPF 설정값이 발송된 이메일에 포함된 SPF 서명과 어느 정도 일치해야 DMARC 정책을 통과했다고 판단할지 여부를 정의한다. 
SPF 서명은 DKIM과 마찬가지로 일반 사용 환경에서는 확인할 수 없고 &#39;이메일 원본&#39;의 헤더 값에서 확인할 수 있다.</p>
<ul>
<li>s: MAIL FROM의 도메인과 발신자 주소의 도메인이 완전히 일치해야 발송을 허용한다.</li>
<li>r: MAIL FROM의 도메인과 발신자 주소의 도메인이 부분적으로 일치해도 발송을 허용한다.</li>
</ul>
<p>기본적으로 r 값으로 설정된다.</p>
<p>이 태그는 선택사항이다.</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/dd333908-93dc-4896-a4d7-5e9edad6f1f9/image.png" alt=""></p>
<p>메일을 보내서 원본메일을 확인해보면 모든값이 PASS로 되어있다면 설정은 성공이다!</p>
<hr>
<p>참고)
<a href="https://help.stibee.com/hc/ko/articles/8640149224591-DMARC-%EC%84%A4%EC%A0%95%EC%9D%B4-%EB%AD%94%EA%B0%80%EC%9A%94">https://help.stibee.com/hc/ko/articles/8640149224591-DMARC-%EC%84%A4%EC%A0%95%EC%9D%B4-%EB%AD%94%EA%B0%80%EC%9A%94</a></p>
<p><a href="https://help.stibee.com/hc/ko/articles/8640149224591-DMARC-%EC%84%A4%EC%A0%95%EC%9D%B4-%EB%AD%94%EA%B0%80%EC%9A%94">https://help.stibee.com/hc/ko/articles/8640149224591-DMARC-%EC%84%A4%EC%A0%95%EC%9D%B4-%EB%AD%94%EA%B0%80%EC%9A%94</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[zip 파일로 업로드]]></title>
            <link>https://velog.io/@wony_yoon/zip-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EC%97%85%EB%A1%9C%EB%93%9C</link>
            <guid>https://velog.io/@wony_yoon/zip-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EC%97%85%EB%A1%9C%EB%93%9C</guid>
            <pubDate>Thu, 22 Feb 2024 07:31:38 GMT</pubDate>
            <description><![CDATA[<p>파일 업로드를 하는데 파일을 zip 파일로 만들어서 올려야했다 
js로 구현했고 드래그앤드롭이 가능하고 올라간 파일은 리스트로 보이며 삭제가 가능하고
추가 업로드도 가능하다</p>
<pre><code class="language-js">  &lt;body&gt;
    &lt;div id=&quot;fileDropArea&quot; class=&quot;fileDropArea&quot;&gt;
      &lt;p&gt;Drag &amp; Drop files here&lt;/p&gt;
      &lt;p&gt;or&lt;/p&gt;
      &lt;input
        type=&quot;file&quot;
        id=&quot;file-input&quot;
        multiple
        accept=&quot;.stl,.obj,.zip&quot;
        style=&quot;display: none&quot;
      /&gt;
      &lt;label for=&quot;file-input&quot; style=&quot;cursor: pointer&quot;
        &gt;Click here to select files (STL, OBJ, ZIP)&lt;/label
      &gt;
    &lt;/div&gt;
    &lt;ul id=&quot;file-list&quot;&gt;&lt;/ul&gt;
    &lt;button id=&quot;clear-list&quot;&gt;휴지통&lt;/button&gt;
    &lt;button id=&quot;submit&quot;&gt;제출&lt;/button&gt;

    &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js&quot;&gt;&lt;/script&gt;
    &lt;script&gt;
      const MAX_FILES = 100;

      const fileList = document.getElementById(&quot;file-list&quot;);
      const fileInput = document.getElementById(&quot;file-input&quot;);
      const clearListBtn = document.getElementById(&quot;clear-list&quot;);
      const submitBtn = document.getElementById(&quot;submit&quot;);
      const fileDropArea = document.getElementById(&quot;fileDropArea&quot;);

      let files = [];

      fileInput.addEventListener(&quot;change&quot;, handleFileUpload);
      clearListBtn.addEventListener(&quot;click&quot;, clearFileList);
      submitBtn.addEventListener(&quot;click&quot;, submitFiles);

      fileDropArea.addEventListener(&quot;dragover&quot;, handleDragOver);
      fileDropArea.addEventListener(&quot;dragleave&quot;, handleDragLeave);
      fileDropArea.addEventListener(&quot;drop&quot;, handleDrop);

      function handleDragOver(event) {
        event.preventDefault();
        fileDropArea.classList.add(&quot;dragover&quot;);
      }

      function handleDragLeave() {
        fileDropArea.classList.remove(&quot;dragover&quot;);
      }

      function handleDrop(event) {
        event.preventDefault();
        fileDropArea.classList.remove(&quot;dragover&quot;);
        const droppedFiles = event.dataTransfer.files;
        handleFiles(droppedFiles);
      }

      function handleFileUpload(event) {
        const newFiles = Array.from(event.target.files);
        handleFiles(newFiles);
      }

      function handleFiles(newFiles) {
        if (files.length + newFiles.length &gt; MAX_FILES) {
          alert(&quot;파일 갯수는 최대 &quot; + MAX_FILES + &quot;개까지 가능합니다.&quot;);
          return;
        }

        Array.from(newFiles).forEach((file) =&gt; {
          if (file instanceof File &amp;&amp; isValidFileType(file)) {
            files.push(file);
          } else {
            alert(&quot;지원되지 않는 파일 형식입니다: &quot; + file.name);
          }
        });

        renderFileList();
        toggleClearListBtn();
      }

      function isValidFileType(file) {
        const allowedTypes = [&quot;.stl&quot;, &quot;.obj&quot;, &quot;.zip&quot;];
        const fileType = file.name
          .slice(file.name.lastIndexOf(&quot;.&quot;))
          .toLowerCase();
        return allowedTypes.includes(fileType);
      }

      function renderFileList() {
        fileList.innerHTML = &quot;&quot;;
        files.forEach((file, index) =&gt; {
          const li = document.createElement(&quot;li&quot;);
          li.textContent = file.name;
          const removeBtn = document.createElement(&quot;button&quot;);
          removeBtn.textContent = &quot;x&quot;;
          removeBtn.addEventListener(&quot;click&quot;, () =&gt; removeFile(index));
          li.appendChild(removeBtn);
          fileList.appendChild(li);
        });
      }

      function removeFile(index) {
        files.splice(index, 1);
        renderFileList();
        toggleClearListBtn();
      }

      function clearFileList() {
        files = [];
        renderFileList();
        toggleClearListBtn();
      }

      function toggleClearListBtn() {
        clearListBtn.style.display = files.length &gt; 0 ? &quot;inline-block&quot; : &quot;none&quot;;
      }

      function submitFiles() {
        const zip = new JSZip();
        const folder = zip.folder(&quot;uploads&quot;);

        files.forEach((file) =&gt; {
          folder.file(file.name, file);
        });

        folder
          .generateAsync({ type: &quot;blob&quot; })
          .then((blob) =&gt; {
            // 여기서 blob을 API로 전송하면 됩니다.
            console.log(blob);
            const zipFilename = &quot;files.zip&quot;;
            const a = document.createElement(&quot;a&quot;);
            a.href = URL.createObjectURL(blob);
            a.download = zipFilename;
            a.click();
          })
          .catch((error) =&gt; console.error(&quot;Error creating zip file:&quot;, error));
      }
    &lt;/script&gt;
  &lt;/body&gt;

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[image 화질 개선기]]></title>
            <link>https://velog.io/@wony_yoon/image-%ED%99%94%EC%A7%88-%EA%B0%9C%EC%84%A0%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/image-%ED%99%94%EC%A7%88-%EA%B0%9C%EC%84%A0%EA%B8%B0</guid>
            <pubDate>Mon, 29 Jan 2024 10:30:41 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-html">&lt;img src&quot;/resource/images/2024-update-ko.png&quot; /&gt;</code></pre>
<p>이 간단한 코드 한줄에서 시작된 험난한 여정기를 기록해보자</p>
<p>기능 개선 업데이트 팝업창을 띄우기로 하고 이미지를 받아서 평소와같이 <code>&lt;img/&gt;</code> 를 이용하였다</p>
<p>근데 이미지가 묘하게 깨져보이는 문제가 발생했다
<img src="https://velog.velcdn.com/images/wony_yoon/post/f43dae0b-280e-4dbd-b61f-e139ca5b275e/image.png" alt=""></p>
<p>언뜻 봐서는 잘 구분이 안가지만 자세히 보면 볼 수록 이미지 픽셀이 깨져보였다 </p>
<p>그래서 개선해보기로하고 구글링을 시작했다</p>
<h1 id="1-확장자-바꾸기">1. 확장자 바꾸기</h1>
<p>처음에 받은 이미지 확장자는     <code>png</code> 였다
그래서 <code>jpg</code>, <code>bmp</code>로 바꿔보았지만 <code>jpg</code>는 압축과정에서 손실이 컸고<code>bmp</code>는 이미지 용량이 너무 커서 사용하지 않기로했다</p>
<p><code>svg</code>로도 적용시켜보았는데 선명해졌지만 글자의 픽셀이 깨져보이는 문제가 있어서 사용하지 않기로 했다</p>
<p>마지막으로 web에서 가장 좋다고 알려진 <code>webp</code>로 변경해보았고 이미지용량이 눈에띄게 줄었다 
그래서 <code>webp</code>를 사용하기로 하고 다른 방법을 찾기 시작했다</p>
<h1 id="2-css로-해결하기">2. css로 해결하기</h1>
<p>브라우저에 이미지를 올리면 화질이 떨어진다는 내용은 상당히 많았다 그래서 쉽게 글을 찾을 수 있었는데
css로 해결할 사례를 적용시켜보았다</p>
<pre><code class="language-css">  transform: translateZ(0);
  backface-visibility: hidden;
  display: block;
  width: 500px;
  max-width: 500px;
  height: auto;
  image-rendering: -moz-crisp-edges; /* Firefox */
  image-rendering: -o-crisp-edges; /* Opera */
  image-rendering: -webkit-optimize-contrast;/* Webkit (non-standard naming) */
  image-rendering: crisp-edges;
  -ms-interpolation-mode: nearest-neighbor; /* IE (non-standard property) */
</code></pre>
<ol>
<li><p>transform : translateZ(0)
z축을 0으로 적용하여 깊이감을 없앤다.</p>
</li>
<li><p>backface-visibility: hidden;<br>뒷면을 숨겨서 입체감을 없앤다.</p>
</li>
<li><p>image-rendering: -webkit-optimize-contrast;
이미지 랜더링 방식을 바꾸게되면 크롬, 사파리 등 브라우저에서 대비를 최적화하여 이미지 랜더링을 하는데 흐린 문제를 많이 해소해준다.</p>
</li>
</ol>
<p><strong>하지만 이 방법으로도 해결이 되지 않았다</strong></p>
<h1 id="3-canvas로-해결하기">3. canvas로 해결하기</h1>
<p>나는 이 방법을 통해 해결하였다
이미지를 canvas에 그려서 선명한 이미지를 불러오는데 성공했다</p>
<p>사실 이 방법으로 해결된것이 완벽하게 이해되진 않지만 그래도 이유를 두가지 정도로 생각했다</p>
<ol>
<li><p>크기 조절:
<code>&lt;img&gt;</code> 태그를 사용하여 이미지를 불러올 때, HTML에서 지정한 width 및 height 속성에 따라 브라우저가 이미지를 확대 또는 축소하여 표시하는데, 브라우저에서 이미지를 조절하는 알고리즘에 따라 이미지 품질이 감소될 수 있다.</p>
<p>반면 <code>&lt;canvas&gt;</code> 태그를 사용하면 이미지 크기를 직접 지정하고, JavaScript 코드를 사용하여 캔버스에 이미지를 그릴 때 크기를 조절할 수 있습니다. 이로 인해 이미지를 원본 크기에 가깝게 그릴 수 있어 선명한 이미지를 얻을 수 있다.</p>
</li>
<li><p>화면에 그리는 방식:
<code>&lt;img&gt;</code> 태그는 이미지를 화면에 직접 그리는 것이 아니라 이미지 파일 자체를 화면에 출력한다.</p>
<p><code>&lt;canvas&gt;</code> 태그를 사용할 경우에는 JavaScript 코드를 사용하여 이미지를 캔버스에 직접 그리는데 이 과정에서 브라우저는 더욱 세밀한 제어를 통해 이미지를 그릴 수 있으며, 필요한 경우 성능 및 품질을 조절할 수 있다.</p>
</li>
</ol>
<pre><code class="language-html">&lt;canvas id=&quot;canvas&quot; width=&quot;500&quot; height=&quot;500&quot;&gt;&lt;/canvas&gt;
</code></pre>
<pre><code class="language-js">function drawImageToCanvas(imgSrc, canvasId) {
        const canvas = document.getElementById(canvasId);
        const ctx = canvas.getContext(&#39;2d&#39;);
        const img = new Image();

        img.onload = function () {
            // canvas에 이미지 그리기
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

            // 그려진 이미지를 다시 img 태그의 src로 설정
            const canvasImgData = canvas.toDataURL(&#39;image/webp&#39;);
            document.getElementById(&quot;event_img&quot;).src = canvasImgData;
        };

        img.src = imgSrc;
    }


$.ajax({
    url: &quot;https://api.ip.pe.kr/json/&quot;,
    type: &quot;GET&quot;,
    success: function(data) {
      const img = document.getElementById(&quot;event_img&quot;);
      const country_code = data.country_code

      if (country_code === &quot;KR&quot;) {
        drawImageToCanvas(&#39;/resource/images/2024-update-ko.webp&#39;, &#39;canvas&#39;)
      } else {
        drawImageToCanvas(&#39;/resource/images/2024-update-en.webp&#39;, &#39;canvas&#39;)
      }
    },
    error: function() {}
  })
</code></pre>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/07384026-faf0-4bf9-b82a-2a0d0f6870da/image.png" alt=""></p>
<p>사실 블로그에 올린 이미지만 봐서는 큰 차이가 나지 않지만 내가 개발하는 local 환경에서는 유의미한 변화가 생겼다.</p>
<p>다음에도 이런 일이 있다면 다시 사용해봐야겠다.</p>
<hr>
<p>추가</p>
<pre><code class="language-css">canvas {
  image-rendering: optimizeSpeed;             /* 빠른 렌더링을 위해 */
  image-rendering: -moz-crisp-edges;          /* Firefox에 대한 설정 */
  image-rendering: -webkit-optimize-contrast; /* Webkit 브라우저에 대한 설정 */
  image-rendering: optimize-contrast;         /* 표준 설정 */
  image-rendering: pixelated;                 /* 고해상도 표시 */
}</code></pre>
<p>이 설정도 추가해주었더니 이미지의 품질이 더욱 향상되었다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next js 첫 시작페이지 변경하기]]></title>
            <link>https://velog.io/@wony_yoon/Next-js-%EC%B2%AB-%EC%8B%9C%EC%9E%91%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/Next-js-%EC%B2%AB-%EC%8B%9C%EC%9E%91%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 14 Nov 2023 02:55:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wony_yoon/post/95506e7b-0019-44f4-bd83-276973878390/image.png" alt=""></p>
<p>Next.js에서 app routing을 사용할 경우 src/app 안의 폴더 이름명으로 routing이 이루어진다
따라서, src/app/page.tsx의 위치로 첫페이지가 route 되는데 </p>
<p>첫 페이지를 src/app/page.tsx가 아닌 내가 원하는 페이지로 설정하고 싶다면 </p>
<p>&#39; next.config.js &#39; 파일에서 설정을 변경할 수 있다. 
아래와 같이 &#39; next.config.js &#39; 파일에 설정을 추가하면 루트 경로&#39; / &#39; 를 &#39; /main &#39; 경로로 변경할 수 있다.</p>
<p>이렇게 설정하면 첫 페이지가 &#39; /main &#39; 경로에서 시작된다.</p>
<pre><code class="language-js">const nextConfig = {
  async redirects() {
    return [
      {
        source: &quot;/&quot;,
        destination: &quot;/main&quot;,
        permanent: true,
      },
    ];
  },
};

module.exports = nextConfig;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native https 통신 오류]]></title>
            <link>https://velog.io/@wony_yoon/React-Native-https-%ED%86%B5%EC%8B%A0-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@wony_yoon/React-Native-https-%ED%86%B5%EC%8B%A0-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Thu, 02 Nov 2023 00:42:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wony_yoon/post/ff84e5a0-9c84-4276-bb08-cbe3a2a09704/image.png" alt=""></p>
<p>React native로 채팅어플을 개발하던중 안드로이드에서만 https로의 통신이 되지 않았다</p>
<p>웹, ios, postman 모두 정상적으로 통신에 성공해 값을 넘겨 받았는데 
오직 안드로이드에서만 안되니까 막막했다 이 문제 해결에 꽤 오랜 시간이 걸렸다
진짜 안본 블로그가 없을 정도로 간절했다</p>
<p>내가 해결한 방법을 적어보려고 한다</p>
<h2 id="✏️-server단에서-ssl-인증서를-추가해주기">✏️ Server단에서 ssl 인증서를 추가해주기</h2>
<p>내가 원인 분석을 하며 여러 자료를 찾아보니 제일 많이 나온 조언이 ssl인증서를 확인하라는 말이 제일 많았다
<a href="https://www.ssllabs.com/ssltest/index.html">https://www.ssllabs.com/ssltest/index.html</a>
위의 링크에서 확인해보고싶은 url을 입력하면 ssl의 보안상태를 진단해준다 </p>
<p>이 부분은 나의 사수님께서 진행해주신 부분이다
이곳에서 weak라고 나온 부분을 수정해보거나 aws에서 dns caa 부분을 추가해주었다 
그래도 동작하지 않았다</p>
<p>그래서 openssl에서 ssl인증서를 생성하고 추가해보기로했다
<a href="https://saysimple.tistory.com/62">https://saysimple.tistory.com/62</a>
이 블로그를 참고했고 그 뒤로 https로 통신이 가능해졌다</p>
<h2 id="✏️-ssl-ignore-추가해주기">✏️ ssl ignore 추가해주기</h2>
<p>useEffect안에서 get method로 값을 가져오는데 최초 1회는 값이 가져와지지만
그 다음부터는 Typeerror: network request failed 에러가 계속 되었다 </p>
<p>많은 블로그들을 보다가 <a href="https://rubyfaby.medium.com/how-to-ignore-ssl-for-react-native-f808810ffaed">https://rubyfaby.medium.com/how-to-ignore-ssl-for-react-native-f808810ffaed</a>
이 블로그의 내용을 참고하여 나의 프로젝트에도 추가했더니 그 뒤로 정상동작하게 되었다 </p>
<h3 id="1-androidappsrcmainjavacomyourapp-위치에-ignoresslfactoryjava-파일을-만든다">1. /android/app/src/main/java/com/[yourapp]/ 위치에 IgnoreSSLFactory.java 파일을 만든다</h3>
<p>(MainApplicaiton.java와 같은 위치에 만든다)</p>
<pre><code class="language-java">//IgnoreSSLFactory.java
package com.yourapp;  //이 자리는 본인의 package로 변경
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import android.util.Log;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import static android.content.ContentValues.TAG;
public class IgnoreSSLFactory implements OkHttpClientFactory {
    private static final String TAG = &quot;IgnoreSSLFactory&quot;;

    @Override
    public OkHttpClient createNewNetworkModuleClient() {
        try {
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }
@Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }
@Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };
final SSLContext sslContext = SSLContext.getInstance(&quot;SSL&quot;);
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(0, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
                    .writeTimeout(0, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw new RuntimeException(e);
        }
    }
}</code></pre>
<h3 id="2-mainapplicationjava-수정한다">2. MainApplication.java 수정한다</h3>
<ul>
<li><p>import com.facebook.react.modules.network.OkHttpClientProvider; 추가한다</p>
</li>
<li><p>onCreate() 안에  OkHttpClientProvider.setOkHttpClientFactory(new IgnoreSSLFactory()); 추가한다</p>
</li>
</ul>
<pre><code class="language-java">public void onCreate() {    
    super.onCreate();    
    SoLoader.init(this, /* native exopackage */ false);    
    OkHttpClientProvider.setOkHttpClientFactory(new IgnoreSSLFactory()); // 추가한 부분
    initializeFlipper(this,       
    getReactNativeHost().getReactInstanceManager());  
}</code></pre>
<p>나와같이 안드로이드에서만 https통신이 안된다면 ssl인증서를 확인하고 그럼에도 안된다면 
ssl ignore 파일을 추가해 보길 바란다 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[숫자 슬롯머신 이벤트 만들기]]></title>
            <link>https://velog.io/@wony_yoon/%EC%88%AB%EC%9E%90-%EC%8A%AC%EB%A1%AF%EB%A8%B8%EC%8B%A0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/%EC%88%AB%EC%9E%90-%EC%8A%AC%EB%A1%AF%EB%A8%B8%EC%8B%A0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 24 Oct 2023 07:23:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wony_yoon/post/bc863999-4fad-422d-91ff-b845bba67c4a/image.gif" alt=""></p>
<p>어떤 값을 유저에게 눈에띄게 보여주는 방법중에 슬롯머신처럼 보여주는 방법이 있다
이 방법을 구현해보고자 했고 순수 자바스크립트로 구현해보았다</p>
<p>구글링을 통해 방법을 찾고 수정한 끝에 구현에 성공했다</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot; /&gt;
    &lt;title&gt;슬롯머신&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;countTest2&quot;&gt;&lt;/div&gt;
    &lt;script src=&quot;script.js&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code class="language-css">.num {
  display: inline-block;
  width: 10px;
  height: 30px;
  overflow: hidden;
  text-align: center;
}
.num-list {
  display: inline-block;
  width: 10px;
  line-height: 30px;
  margin-top: 0;
  text-align: center;
}
</code></pre>
<pre><code class="language-js">function RollingNum(id, number) {
  const cntBox = document.getElementById(id);
  const cntNum = number;
  const cntLen = cntNum.length;
  const numArr = cntNum.split(&quot;&quot;);
  const delay = 500;
  const speed = 70;
  const numHeight = 80;

  // 입력받은 숫자만큼 칸을 만들어준다 
  for (let i = 0; i &lt; cntLen; i++) {
    const num = document.createElement(&quot;span&quot;);
    num.classList.add(&quot;num&quot;, &quot;idx&quot; + i);
    num.setAttribute(&quot;data-num&quot;, numArr[i]);
    num.style = `height:${numHeight}px;line-height:${numHeight}px;width:${numHeight}px;background-color:#07a2e8;margin:0px 7px;font-size:50px;color:white;border-radius:5px`;
    cntBox.appendChild(num);
    setNum(num, i);
  }

  // 숫자 회전 애니메이션
  function setNum(el, n) {
    setTimeout(function () {
      let no = 0;
      const style = document.createElement(&quot;style&quot;);
      style.innerHTML = `.num {overflow: hidden;}.numList {display: inline-block;margin-top:0;text-align:center;transition: all ${
        speed / 1000
      }s;}`;
      document.body.appendChild(style);

      const numbersDiv = document.createElement(&quot;span&quot;);
      const numbers = &quot;0\n1\n2\n3\n4\n5\n6\n7\n8\n9&quot;;
      numbersDiv.classList.add(&quot;numList&quot;);
      numbersDiv.innerText = numbers;
      el.appendChild(numbersDiv);

      // 초기값 설정
      numbersDiv.style.marginTop = &quot;0px&quot;;

      // margin-top의 값이 바뀌면서 회전하고 입력받은 숫자값에서 멈춤
      const intervalNo = setInterval(function () {
        no++;
        numbersDiv.style = `margin-top:${no * numHeight * -1}px`;
        if (no === 10) {
          clearInterval(intervalNo);
          numbersDiv.style = `margin-top:${
            el.getAttribute(&quot;data-num&quot;) * numHeight * -1
          }px`;
        }
      }, speed);
    }, delay * n);
  }
}

RollingNum(&quot;countTest2&quot;, &quot;28432&quot;);</code></pre>
<hr>
<p>도움- <a href="https://codepen.io/yoon123456/pen/dyaPWgm">https://codepen.io/yoon123456/pen/dyaPWgm</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React native http 통신 허용하기]]></title>
            <link>https://velog.io/@wony_yoon/react-native-release-%ED%9B%84-network-error</link>
            <guid>https://velog.io/@wony_yoon/react-native-release-%ED%9B%84-network-error</guid>
            <pubDate>Sat, 27 May 2023 07:44:09 GMT</pubDate>
            <description><![CDATA[<p>앱개발이 어느정도 완료가 되어가고 테스트를 위해 apk파일을 안드로이드 핸드폰에서 실행했는데...!
axios가 정상 작동하지 않았다 
네트워크 오류라는 메세지 확인 후 구글링을 엄청 해보니</p>
<blockquote>
<p>android 9 이상부터는 http 프로토콜 요청이 기본적으로 막혀있다고 한다</p>
</blockquote>
<p>해결방법은
<img src="https://velog.velcdn.com/images/wony_yoon/post/376aecb4-ee18-417b-bc90-ef20d4b0d162/image.png" alt=""></p>
<p>android/app/src/main/AndroidManifest.xml 
이 위치에 application 안에 아래와 같이 입력해주면된다 </p>
<pre><code class="language-js">&lt;applicaion
  ...
  android:usesCleartextTraffic=&quot;true&quot;
  ...
&gt;</code></pre>
<p>위처럼 해주면 http 통신도 가능해진다 
https 통신을 이용하면 설정해주지 않아도 된다!</p>
<blockquote>
<p>ios의 경우에도 http 통신을 허용하지 않는다 </p>
</blockquote>
<p>http 통신을 허용하기 위해서는 info.plist 를 수정해주면 된다
./ios/[프로젝트명]/info.plist 나 xcode의 프로젝트 폴더의 info.plist 에 추가한다</p>
<pre><code class="language-js">&lt;key&gt;NSAppTransportSecurity&lt;/key&gt;
&lt;dict&gt;
    &lt;key&gt;NSAllowsArbitraryLoads&lt;/key&gt; //  추가
    &lt;true/&gt;                           //  추가
    &lt;key&gt;NSExceptionDomains&lt;/key&gt;
    &lt;dict&gt;
        &lt;key&gt;localhost&lt;/key&gt;
        &lt;dict&gt;
            &lt;key&gt;NSExceptionAllowsInsecureHTTPLoads&lt;/key&gt;
            &lt;true/&gt;
        &lt;/dict&gt;
    &lt;/dict&gt;
&lt;/dict&gt;</code></pre>
<hr>
<p>참고) <a href="https://github.com/facebook/react-native/issues/28551#issuecomment-611085378">https://github.com/facebook/react-native/issues/28551#issuecomment-611085378</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IOS permission error ]]></title>
            <link>https://velog.io/@wony_yoon/IOS-permission-error</link>
            <guid>https://velog.io/@wony_yoon/IOS-permission-error</guid>
            <pubDate>Wed, 10 May 2023 06:21:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wony_yoon/post/43456ce4-3218-49bd-aade-43b020fe3cdb/image.png" alt=""></p>
<blockquote>
<p>No permission handler detected.</p>
</blockquote>
<p>며칠전부터 yarn run ios를 실행하면 제일 처음에 나왔던 에러창 원인이 뭔지 몰라 수많은 구글링과 sample 코드를 비교한 끝에 해결할 수 있었다 </p>
<p>찾고 보니 에러화면에 다 나와있던 해결방법;;</p>
<p>해결 방법은 <strong>package.json</strong>에 </p>
<pre><code class="language-json">&quot;reactNativePermissionsIOS&quot;: [
    &quot;AppTrackingTransparency&quot;,
    &quot;BluetoothPeripheral&quot;,
    &quot;Calendars&quot;,
    &quot;Camera&quot;,
    &quot;Contacts&quot;,
    &quot;FaceID&quot;,
    &quot;LocationAccuracy&quot;,
    &quot;LocationAlways&quot;,
    &quot;LocationWhenInUse&quot;,
    &quot;MediaLibrary&quot;,
    &quot;Microphone&quot;,
    &quot;Motion&quot;,
    &quot;Notifications&quot;,
    &quot;PhotoLibrary&quot;,
    &quot;PhotoLibraryAddOnly&quot;,
    &quot;Reminders&quot;,
    &quot;Siri&quot;,
    &quot;SpeechRecognition&quot;,
    &quot;StoreKit&quot;
  ]</code></pre>
<p>위의 내용을 입력하고 node_modules를 지우고 다시 yarn install , pod install을 진행하였다</p>
<p>그렇게 하니 ios permission과 관련된 에러가 사라졌다!</p>
<p>다시 한번 뇌에 새기는 에러문구를 잘 읽자...😅</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[android release apk 만들기]]></title>
            <link>https://velog.io/@wony_yoon/android-release-apk-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/android-release-apk-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 03 May 2023 06:42:58 GMT</pubDate>
            <description><![CDATA[<p>react native와 sendbird를 이용하여 chatting app을 만들고 있다 
어느정도 코드는 되었고 실제 핸드폰에서 동작하는지 확인하기 위해 apk 파일을 만들어 android폰에 설치하고 동작하는지 확인해보려고 한다 </p>
<p>사수께서 너무나 잘 알려주셔서 잊지않기 위해 기록으로 남기기!</p>
<h1 id="🔑android-key-만들기">🔑android key 만들기</h1>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/b66afa5b-63d5-4f6d-bfda-0a82590b17f1/image.png" alt="">
build 메뉴에서 <strong>Generate Signed Bundle / APK</strong>를 선택한다</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/37f9045b-b3d3-4d16-87fa-16f81a4754e9/image.png" alt="">
<strong>APK</strong>를 선택하고
<img src="https://velog.velcdn.com/images/wony_yoon/post/f8edbc89-d541-466d-b35b-4f7f2e0d00da/image.png" alt="">
<strong>Create new</strong>를 누르면 아래의 화면으로 이동한다</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/f9c03a2d-de12-4b50-afca-9e6b1d27a609/image.png" alt="">
여기서 key를 저장할 경로를 지정하고 파일명을 <strong>key이름.jks</strong>로 지정한다
password와 이름, 회사명, 나라를 입력하면 키는 생성된다 
Country Code는 <strong>KR</strong>로 적으면 된다!
<img src="https://velog.velcdn.com/images/wony_yoon/post/049df413-020b-440c-868c-e00ec2fed455/image.png" alt="">
그럼 이렇게 키가 생성이 되었을 것이다 </p>
<h1 id="🔑android-apk파일-만들기">🔑android apk파일 만들기</h1>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/6997539c-20c7-409e-abd5-b679f316fb99/image.png" alt="">
키의 path가 일치하는지 확인하고 
비밀번호를 입력한다
<img src="https://velog.velcdn.com/images/wony_yoon/post/57836dd0-8a53-4ca2-8853-dedee6060a48/image.png" alt=""></p>
<p><strong>release</strong>를 선택하고 <strong>Create</strong>를 누르면 apk 파일이 생성된다</p>
<p>작업한 폴더에 들어가서 android &gt; app &gt; release 파일에 들어가면 apk 파일이 있을것이다
android 환경의 폰에 전송해서 설치하면 사용이 가능하다!</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/7bbfadfc-f337-4e9a-8ec3-082be23856c8/image.png" alt="">
<img src="https://velog.velcdn.com/images/wony_yoon/post/6bb781f3-047b-4113-9523-07de8fb202d0/image.png" alt=""></p>
<p>apk 파일 만들기 끄읕!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[component 이름 바꾸기 ]]></title>
            <link>https://velog.io/@wony_yoon/component-%EC%9D%B4%EB%A6%84-%EB%B0%94%EA%BE%B8%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/component-%EC%9D%B4%EB%A6%84-%EB%B0%94%EA%BE%B8%EA%B8%B0</guid>
            <pubDate>Tue, 14 Feb 2023 04:56:16 GMT</pubDate>
            <description><![CDATA[<p>나는 emotion styled component를 통해 style을 만들어주고 있는데 
가끔 component에 div나 span등 여러 값을 바꾸고 싶은 경우 아래처럼 하면 된다 
밑의 예시는 h1~h6 까지의 태그를 만들어줄 수 있다 </p>
<pre><code class="language-js">export const aaa = styled(({ h, ...props }: any) =&gt; {
  const H = `h${h}`;
  return &lt;H {...props} /&gt;;
})`
  font-size: ${(props) =&gt; [props.h - 1]};
  font-weight: 300;
  letter-spacing: 1px;
  line-height: 1.375;
`;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[create react 초기셋팅]]></title>
            <link>https://velog.io/@wony_yoon/create-react-%EC%B4%88%EA%B8%B0%EC%85%8B%ED%8C%85</link>
            <guid>https://velog.io/@wony_yoon/create-react-%EC%B4%88%EA%B8%B0%EC%85%8B%ED%8C%85</guid>
            <pubDate>Tue, 03 Jan 2023 02:29:47 GMT</pubDate>
            <description><![CDATA[<p> 내가 보려고 적는 초기 셋팅 방법
 리액트와 타입스크립트를 기반으로 하는 셋팅방법이다 </p>
<h2 id="✏️react">✏️React</h2>
<p> <strong>npx create-react-app &quot;원하는 파일명&quot; -—template typescript</strong></p>
<h3 id="-타입스크립트로-설치가-안될-경우">-타입스크립트로 설치가 안될 경우</h3>
<p> <strong>yarn add typescript @types/node @types/react @types/react-dom @types/jest</strong></p>
<h3 id="tsconfigjson-생성">tsconfig.json 생성</h3>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es6&quot;,
    &quot;lib&quot;: [
      &quot;dom&quot;,
      &quot;dom.iterable&quot;,
      &quot;esnext&quot;
    ],
    &quot;baseUrl&quot;: &quot;./src&quot;, 
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;
  },
  &quot;include&quot;: [
    &quot;src&quot;
  ],
  &quot;exclude&quot;: [
    &quot;node_modules&quot;   
  ],
}</code></pre>
<h2 id="✏️eslint">✏️Eslint</h2>
<p><strong>yarn run eslint --init
yarn add @typescript-eslint/parser</strong>
<img src="https://velog.velcdn.com/images/wony_yoon/post/386de323-a142-4154-bbac-55e760c16a9e/image.png" alt=""></p>
<h2 id="✏️prettier">✏️Prettier</h2>
<p><strong>yarn add --dev --exact prettier
echo {}&gt; .prettierrc.json</strong></p>
<h2 id="✏️eslint와-prettier-연결">✏️Eslint와 Prettier 연결</h2>
<p><strong>yarn add eslint-config-prettier --dev</strong></p>
<h2 id="✏️react-router-dom">✏️React-router-dom</h2>
<p> <strong>yarn add react-router-dom</strong></p>
<h2 id="✏️react-hook-form">✏️React-hook-form</h2>
<p>** yarn add react-hook-form**</p>
<h2 id="✏️axios">✏️Axios</h2>
<p><strong>yarn add axios</strong></p>
<h2 id="✏️recoil">✏️Recoil</h2>
<p><strong>yarn add recoil</strong></p>
<h2 id="✏️emotion">✏️Emotion</h2>
<p><strong>yarn add @emotion/react
yarn add @emotion/styled</strong></p>
<h2 id="✏️ant-design">✏️Ant-design</h2>
<p><strong>yarn add antd</strong></p>
<h2 id="✏️react-bootstrap">✏️React-bootstrap</h2>
<p><strong>yarn add react-bootstrap
yarn add @types/react-bootstrap</strong></p>
<h2 id="✏️dropzone">✏️Dropzone</h2>
<p><strong>yarn add react-dropzone</strong></p>
<h2 id="✏️i18next">✏️i18next</h2>
<p><strong>yarn add react-i18next
yarn add @types/i18next</strong></p>
<h2 id="✏️uuid">✏️uuid</h2>
<p><strong>yarn add uuid
yarn add @types/uuid</strong></p>
<h2 id="✏️dayjs">✏️Dayjs</h2>
<p><strong>yarn add dayjs</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[axios.create]]></title>
            <link>https://velog.io/@wony_yoon/axios.create</link>
            <guid>https://velog.io/@wony_yoon/axios.create</guid>
            <pubDate>Tue, 06 Dec 2022 12:30:32 GMT</pubDate>
            <description><![CDATA[<p>axios를 알아갈 수록 아직도 모르는게 너무 많다🥲</p>
<p>최근 headers에 같은 값을 넘겨주어야하는 상황이 생겼는데 
매번 적어주기엔 너무 귀찮더라,,, (나 벌써 개발자 마인드인건가🧐)</p>
<p>graphql에서는 컴포넌트로 뺐던 기억이 나서 axios도 분명이 있겠구나 싶어서 공부해봤다!</p>
<h1 id="✏️-기존의-코드">✏️ 기존의 코드</h1>
<pre><code class="language-js">const sendEmail = async () =&gt; {
    try {
      const response = await axios.post(SENDNAVEREMAIL, {
        email,
      },{
          headers:{
                    &quot;Content-Type&quot;: &quot;application/json&quot;,
                     Authorization: `Bearer ${accessToken}`,
                  },
      });
      alert(`${email}로 메일이 전송 되었습니다`);
      console.log(response);
    } catch (error) {
      console.log(error);
    }
  };</code></pre>
<p>headers에 accessToken의 값을 받아와서 Authorization부분에 넣어주어야하는데 
post 요청을 보내는 함수 마다 headers에 accessToken의 값을 받아와서 Authorization에 넣어주어야한다면 너무나 비효율적인 코드가 될것이다 그래서 axios를 분리하여 사용할 수 있는 것이 
<code>axios.create</code>이다 </p>
<h1 id="✏️-axioscreate">✏️ axios.create</h1>
<pre><code class="language-js">import axios from &quot;axios&quot;;

const baseURL = process.env.REACT_APP_DB_HOST;

const authInstance = () =&gt; {
   const token = &quot;accessToken&quot;;
   const instance = axios.create({
     baseURL,
     headers: {
       &quot;Content-Type&quot;: &quot;application/json&quot;,
       Authorization: `bearer ${token}`,
     },
     ...options,
   });

   return instance;
 };

export const instance = authInstance

</code></pre>
<p>이렇게 만들어주면 요청할때마다 accssToken값을 넣어주지 않아도 
instance 컴포넌트를 불러와서 사용하면 토큰값을 넘겨줄 수 있다 </p>
<p>baseUrl도 적어주기 때문에 post 요청시 주소를 적어주는 부분에는 baseUrl을 제외한 뒷부분만 적어주면 된다 </p>
<h1 id="✏️-수정된-코드">✏️ 수정된 코드</h1>
<pre><code class="language-js">const sendEmail = async () =&gt; {
    try {
      const response = await instance.post(SENDNAVEREMAIL, {
        email,
      });
      alert(`${email}로 메일이 전송 되었습니다`);
      console.log(response);
    } catch (error) {
      console.log(error);
    }
  };

</code></pre>
<p>간단하고 여러번 사용이 가능한 코드가 되었다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sendbird 삽질기2]]></title>
            <link>https://velog.io/@wony_yoon/Sendbird-%EC%82%BD%EC%A7%88%EA%B8%B02</link>
            <guid>https://velog.io/@wony_yoon/Sendbird-%EC%82%BD%EC%A7%88%EA%B8%B02</guid>
            <pubDate>Tue, 06 Dec 2022 11:54:56 GMT</pubDate>
            <description><![CDATA[<h1 id="💬-내가-구현하고자-했던-방향">💬 내가 구현하고자 했던 방향</h1>
<ul>
<li>채팅목록 분리 </li>
<li>채팅창분리</li>
<li>환자이름으로 만들어진 채팅방에 의뢰자와 제작자가 자동으로 초대하고 첫 메세지 발송하기 </li>
<li>모바일에서도 채팅이 가능하고 실시간 연동이 가능해야한다 </li>
</ul>
<p>더 많은 기능이 필요했지만 일단 이렇게 크게 4가지 기능이 가장 중요했다 </p>
<h1 id="💬-모바일에서도-채팅이-가능하고-실시간-연동이-가능해야한다">💬 모바일에서도 채팅이 가능하고 실시간 연동이 가능해야한다</h1>
<p>pwa를 도입하여 모바일에서 다운로드 받으면 어플이 생기고 그곳에서 연동이 가능하게 하였다 
실제 내 핸드폰 화면이다 
<img src="https://velog.velcdn.com/images/wony_yoon/post/19b92450-9c4e-4a72-ba3c-589b65c29a35/image.PNG" alt=""></p>
<p>pwa를 다운로드 받으면 바탕화면에 아이콘이 생긴다
pwa를 모른다면? <a href="https://velog.io/@wony_yoon/PWA">바로여기!</a></p>
<p>들어가보면 반응형을 구현하지 못해 세로로 보면 잘려보이지만 가로로 돌려서 보면 아주 잘 보인다 
<img src="https://velog.velcdn.com/images/wony_yoon/post/452b41e7-6ebb-4e91-8f0a-4a42d6b5ecf1/image.PNG" alt=""></p>
<p>웹과 앱 모두 사용이 가능하게는 만들었다 </p>
<h1 id="💬-채팅방과-채팅목록-분리">💬 채팅방과 채팅목록 분리</h1>
<ul>
<li><p>채팅목록 컴포넌트 </p>
<pre><code class="language-js">&lt;ChannelList
   onChannelSelect={(channel: GroupChannel) =&gt; setUrl(channel.url)}
/&gt;</code></pre>
<p>docs와 github를 찾아보니 위의 컴포넌트를 사용하면 채팅목록만 빼낼 수 있었다 
클릭하면 그 채널의 고유 url을 받아와 채팅 컴포넌트에 넘겨주면 채팅컴포넌트에서는 내가 채팅목록에서 선택한 그 채팅방이 나타나게된다 
그래서 url을 state로 관리하였다 </p>
</li>
<li><p>채팅 컴포넌트 </p>
<pre><code class="language-js">&lt;Channel channelUrl={url} showSearchIcon={true}&gt;&lt;/Channel&gt;</code></pre>
<p>setUrl로 저장된 url을 channelUrl에 넣어주면 그 채팅방이 열린다 </p>
</li>
<li><p>채팅설정</p>
<pre><code class="language-js">&lt;ChannelSettings
  channelUrl={url}
  onCloseClick={onCancle}
&gt;&lt;/ChannelSettings&gt;</code></pre>
<p>해당 채팅방의 설정을 변경하는 컴포넌트이다 </p>
</li>
</ul>
<p>정말 간단하게 정리만해서 이정도이지 더욱더 수많은 컴포넌트들이 존재한다 
이 이상 정리하는건 내가 너무 힘들어서 간단하게 중요한거만 소개를 했다 </p>
<h1 id="💬-환자이름으로-만들어진-채팅방에-의뢰자와-제작자가-자동으로-초대되고-첫-메세지-발송하기">💬 환자이름으로 만들어진 채팅방에 의뢰자와 제작자가 자동으로 초대되고 첫 메세지 발송하기</h1>
<p>이부분은 api가 사용되어야했다
<a href="https://www.postman.com/sendbird/workspace/sendbird-platform-api/overview">여기</a>에 가면 postman을 통해 api를 확인할 수 있다 </p>
<p>sendbird api는 </p>
<pre><code>https://api-{나의 appId}.sendbird.com/v3/{원하는 기능}</code></pre><p>이런식으로 사용이 가능하다 
나는 원하는기능부분에 밑의 두가지를 이용하였다 </p>
<ul>
<li>group_channels</li>
<li>group_channels/{url}/message</li>
</ul>
<p>순서는 이러하다</p>
<ol>
<li>환자의 이름을 props로 받아온다 </li>
<li>나의 아이디와 방에 초대할 아이디 값, 방이름을 넘겨준다 </li>
<li>방이 만들어진다 </li>
<li>첫 메세지를 보낸다 (방만 만들고 메세지를 보내지 않으면 상대방에게 방이 보이지 않기 때문)</li>
</ol>
<pre><code class="language-js">  const getUser = async () =&gt; {
    try {
      const response = await axios.post(
        `https://api-${process.env.REACT_APP_SENDBIRD_APP_KEY}.sendbird.com/v3/group_channels`,
        {
          // 방에 초대하려는 유저의 아이디를 배열안에 담아준다 
          user_ids: [&quot;yw01124&quot;, &quot;치마걸&quot;],
          // 방의 이름 설정하는 부분 (환자의 이름을 방이름으로 하기 위해 props로 받아옴)
          name: patientName,
        },
        {
          // headers에 Api-Token 값을 넣어주지 않으면 방 생성 실패 
          headers: {
             &quot;Api-Token&quot;: process.env.REACT_APP_SENDBIRD_API_TOKEN,
             &quot;Content-Type&quot;: &quot;application/json&quot;,
             Accept: &quot;application/json&quot;,
          },
        }
      );
      // 생성된 방의 url state에 저장    
      setUrl(response.data.channel_url);
      const url = response.data.channel_url;
      try {
        const response = await axios.post(
          `https://api-${process.env.REACT_APP_SENDBIRD_APP_KEY}.sendbird.com/v3/group_channels/${url}/messages`,
          {
            // 메세지를 유저가 보낼지 관리자모드로 보낼지 정할 수 있음 
            message_type: &quot;ADMM&quot;, // ADMM or MESG
            user_id: &quot;yw01124&quot;,
            message: `${patientName}님의 케이스가 접수되었습니다 `,
          },
          {
            // headers에 Api-Token 값을 넣어주지 않으면 메세지 전송 실패
            headers: {
              &quot;Api-Token&quot;: process.env.REACT_APP_SENDBIRD_API_TOKEN,
              &quot;Content-Type&quot;: &quot;application/json&quot;,
              Accept: &quot;application/json&quot;,
            },
          }
        );
      } catch (error) {
        console.log(error);
      }
    } catch (error) {
      console.log(error);
    }
  };
</code></pre>
<p>이러한 방법을 통해 방을 만들고 메세지 보내기까지 성공하였다</p>
<p>하지만,,, 프로젝트를 진행하다보면 여러일이 있는법,,,
예상치 못한 난관에 부딪혔다 
그래서 현재 버전에는 센드버드를 적용하지 않기로 했다 
그동안 공부한걸 못써서 아쉽기도 하지만 이번에 많은 공부를 했고 다음에 사용할 수도 있기에 아쉽지 않다!
나의 스킬이 하나 업된것 같아서 뿌듯하다😆</p>
<hr>
<p>참고
<a href="https://sendbird.com/docs/uikit/v3/react/overview">https://sendbird.com/docs/uikit/v3/react/overview</a>
<a href="https://www.postman.com/sendbird/workspace/sendbird-platform-api/overview">https://www.postman.com/sendbird/workspace/sendbird-platform-api/overview</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sendbird 삽질기]]></title>
            <link>https://velog.io/@wony_yoon/Sendbird-%EC%82%BD%EC%A7%88%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/Sendbird-%EC%82%BD%EC%A7%88%EA%B8%B0</guid>
            <pubDate>Tue, 06 Dec 2022 08:40:57 GMT</pubDate>
            <description><![CDATA[<p>채팅 기능을 한번 만들어보자는 대표님의 말씀이 있으셨고 직접구현하기에는 시간도 부족하니 센드버드를 사용해보자하셔서 약 2주간 공부를 했다</p>
<p>2주간의 기록을 몰아 쓰다보니 정리도 잘 안되겠지만 그래도 기록으로 남겨보려고 한다 
참고로 센드버드는 진짜 구글링해도 찾기가 어렵다ㅜ docs만보고 해쳐나가야한다ㅜㅜ</p>
<h1 id="💬-센드버드-도입-계기">💬 센드버드 도입 계기</h1>
<p>대표님께서 센드버드가 글로벌기업으로 성장하는데에는 그 편리성이 분명히 있을것이라고 하셔서 도입을 하게 되었다 
쉽게 말하자면 대기업인데는 이유가 있을거야! 라고 나는 생각이 들었다ㅎㅎ
내가 직접 구현하기에는 나의 개발 실력은 아주 처참하기에,,, 라이브러리 사용은 언제나 대환영이다 </p>
<h1 id="💬-센드버드-부딪혀보기">💬 센드버드 부딪혀보기</h1>
<p>참고로 나는 react로 개발중이다 
일단 라이브러리를 npm에서 찾아서 설치부터 했다!</p>
<pre><code>yarn add sendbird
yarn add @sendbird/uikit-react</code></pre><p>센드버드를 실행하기위해서는 app key가 꼭 필요한데
key 발급은 센드버드 홈페이지에서 회원가입하면 발급이 가능하다 </p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/4343b58e-bb05-431b-8a93-1ccdd9b115fb/image.png" alt=""></p>
<p>짠 바로 이렇게 채팅창이 나왔다 
바로 아래의 코드만으로! </p>
<pre><code class="language-js">&lt;SendbirdApp
    appId={APP_ID}
    userId={USER_ID}
    theme=&quot;light&quot;
    showSearchIcon={true}
    nickname={&quot;예르&quot;}
/&gt;</code></pre>
<ul>
<li><p><strong>appId</strong>는 센드버드에서 발급받은 key여서 외부유출이 안되도록 .env파일에서 관리하였다 
.env를 모른다면 <a href="https://velog.io/@wony_yoon/.env">바로여기로!</a></p>
</li>
<li><p><strong>userId</strong>는 채팅을 하는 본인의 아이디가 들어간다 나는 변수로 관리하였다 </p>
</li>
<li><p><strong>theme</strong>는  light / dark 로 모드가 변환된다</p>
</li>
<li><p><strong>showSearchIcon</strong>은 채팅창 오른쪽 위에 검색 아이콘을 표시할 것인지를 boolean으로 정한다</p>
</li>
<li><p><strong>nickname</strong>은 사용할 닉네임이다!</p>
</li>
</ul>
<p>이 외에도 많은 기능이 있는데 찾아보니 엄청 많았다 👇</p>
<pre><code class="language-js">export interface AppProps {
    appId: string;
    userId: string;
    accessToken?: string;
    customApiHost?: string,
    customWebSocketHost?: string,
    theme?: &#39;light&#39; | &#39;dark&#39;;
    userListQuery?(): UserListQuery;
    nickname?: string;
    profileUrl?: string;
    dateLocale?: Locale;
    allowProfileEdit?: boolean;
    disableUserProfile?: boolean;
    disableMarkAsDelivered?: boolean;
    showSearchIcon?: boolean;
    renderUserProfile?: (props: RenderUserProfileProps) =&gt; React.ReactNode | React.ReactElement;
    onProfileEditSuccess?(user: User): void;
    config?: SendbirdProviderConfig;
    useReaction?: boolean;
    useMessageGrouping?: boolean;
    stringSet?: Record&lt;string, string&gt;;
    colorSet?: Record&lt;string, string&gt;;
    imageCompression?: {
      compressionRate?: number,
      resizingWidth?: number | string,
      resizingHeight?: number | string,
    };
    replyType?: ReplyType;
    disableAutoSelect?: boolean;
    isReactionEnabled?: boolean,
    isTypingIndicatorEnabledOnChannelList?: boolean,
    isMessageReceiptStatusEnabledOnChannelList?: boolean,
  }</code></pre>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/f646cc15-1b55-4c29-92c9-a716402b69a3/image.png" alt="">
위처럼 유저를 추가할 수도 있고 
<img src="https://velog.velcdn.com/images/wony_yoon/post/49bbc43d-b534-48e8-8c64-b719e07a32a7/image.png" alt="">
채팅방을 나갈 수도 있고 
<img src="https://velog.velcdn.com/images/wony_yoon/post/ad911b09-fbdf-4302-b49c-3f537158ff7a/image.png" alt="">
채팅방의 정보를 알고 여러 기능을 이용할 수 있으며
<img src="https://velog.velcdn.com/images/wony_yoon/post/87a47745-741e-4205-88ff-12b3d0760b6e/image.png" alt="">
채팅방의 이름과 사진 변경 또한 가능하다
<img src="https://velog.velcdn.com/images/wony_yoon/post/1f26a62e-ef06-47f1-83ed-064278b67f0b/image.png" alt="">
검색기능까지 맞춘 만능 센드버드이다 </p>
<p>이것이 단 하나의 컴포넌트로 만들어진거라니....!!!</p>
<p>아니 이렇게 쉽게 채팅창을 만든다고...? 
말도 안돼 라는 생각이 들었다 
그럼 나 개발 끝난건가..? 라는 생각이 들었지만 
회사 프로젝트에 반영을 시키려면 채팅창의 변형이 필요했다 
그럼 그렇지 개발이 이렇게 쉬울리가 없다 </p>
<p><a href="https://velog.io/@wony_yoon/Sendbird-%EC%82%BD%EC%A7%88%EA%B8%B02">2탄으로 이동</a> </p>
<hr>
<p>참고 
<a href="https://github.com/sendbird/sendbird-uikit-react">https://github.com/sendbird/sendbird-uikit-react</a>
<a href="https://sendbird.github.io/sendbird-uikit-react/?path=/story/app-component--korean">https://sendbird.github.io/sendbird-uikit-react/?path=/story/app-component--korean</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PWA]]></title>
            <link>https://velog.io/@wony_yoon/PWA</link>
            <guid>https://velog.io/@wony_yoon/PWA</guid>
            <pubDate>Tue, 22 Nov 2022 13:58:57 GMT</pubDate>
            <description><![CDATA[<h1 id="✏️-pwa란">✏️ PWA란?</h1>
<blockquote>
<p>PWA는 웹과 네이티브 앱의 기능 모두의 이점을 갖도록 수 많은 특정 기술과 표준 패턴을 사용해 개발된 웹 앱입니다.</p>
</blockquote>
<p>MDN 공식문서에 있는 내용이다 </p>
<p>웹개발을 통해 앱으로도 보여줄 수 있는 장점이 있으니 앱개발을 따로 하지 않아도 되고<br>푸시 알림이나 오프라인 지원과 같은 네이티브 앱의 특징들도 전부 제공할 수 있어 그 장점이 매우 크다 </p>
<h1 id="✏️-pwa의-3가지-중요-구성-요소">✏️ PWA의 3가지 중요 구성 요소</h1>
<h3 id="1-보안-연결https">1. 보안 연결(HTTPS)</h3>
<p>PWA는 신뢰할 수 있는 연결 상태에서만 동작하기 때문에, 보안 연결을 통해서 서비스를 제공해야 합니다. 
이건 단지 보안상의 이유 때문만은 아니고, 사용자들의 신뢰를 얻기 위해서도 아주 중요한 부분입니다.</p>
<h3 id="2-서비스-작업자service-worker">2. 서비스 작업자(service worker)</h3>
<p>서비스 작업자는 백그라운드에서 실행되는 스크립트입니다. 
서비스 작업자는 네트워크와 관련된 요청의 처리를 도와주기 때문에, 여러분은 그 점에 대해서는 걱정하지 않고 더욱 복잡한 작업을 수행할 수 있습니다.</p>
<h3 id="3-매니페스트-파일manifest-file-설정-파일">3. 매니페스트 파일(manifest file, 설정 파일)</h3>
<p>이것은 제이슨(JSON, 용량이 적은 데이터를 교환하기 위한 형식) 파일이며, PWA가 표시되고 기능하는 방식에 대한 정보들이 포함되어 있는 것입니다. 여기에서는 PWA의 이름, 설명, 아이콘, 색상 등을 지정할 수 있습니다.</p>
<h1 id="✏️-pwa-시작하기">✏️ PWA 시작하기</h1>
<pre><code class="language-bash">npx create-react-app my-app --template pwa-typescript</code></pre>
<p>위의 명령어를 입력하면 리액트와 타입스크립트를 기반으로 한 pwa template이 만들어진다 </p>
<p>기존의 템플릿과는 다른 파일이 있을것이다
<img src="https://velog.velcdn.com/images/wony_yoon/post/8042519c-fe49-4dc7-b3be-aa1e043218c8/image.png" alt="">
<img src="https://velog.velcdn.com/images/wony_yoon/post/0fe3b21e-e196-4648-91b6-8e4ba9731f87/image.png" alt=""></p>
<h2 id="service-workerts">service-worker.ts</h2>
<p>서비스 워커는 웹 응용 프로그램, 브라우저, 그리고 (사용 가능한 경우) 네트워크 사이의 프록시 서버 역할을 한다.
서비스 워커의 개발 의도는 여러가지가 있지만, 그 중에서도 효과적인 오프라인 경험을 생성하고, 네트워크 요청을 가로채서 네트워크 사용 가능 여부에 따라 적절한 행동을 취하고, 서버의 자산을 업데이트할 수 있다. 또한 푸시 알림과 백그라운드 동기화 API로의 접근도 제공한다.</p>
<p>빌드시에 service-worker.js 파일을 생성하기 위해서 index.js 파일에서 아래의 코드로 변경한다.</p>
<pre><code class="language-js">serviceWorkerRegistration.register();</code></pre>
<h2 id="manifestjson">manifest.json</h2>
<ul>
<li>short_name : 앱 이름 ,아이콘 아래에 표시할 이름</li>
<li>name : 앱의 풀 네임</li>
<li>icons : 앱 아이콘 설정, 플랫폼마다 요구하는 아이콘 크기가 다름</li>
<li>start_url : 앱 클릭시 처음 뜨는 페이지 설정</li>
<li>display : 앱을 실행했을 때 브라우저 상단바를 제거할지 말지 설정</li>
<li>theme_color :    테마 색상 설정</li>
<li>background_color : 배경색 설정</li>
</ul>
<h2 id="yarn-build">yarn build</h2>
<p>프로젝트를 빌드하고 확인하기 위해 github-page를 이용해 배포했다 (이부분은 따로 포스팅하겠다)
배포 후 주소입력창에 보면
<img src="https://velog.velcdn.com/images/wony_yoon/post/1bd68f7f-1d6b-43a6-872d-7460b81c47d2/image.png" alt="">
다운로드 버튼이 있을것이고 누르면 앱을 설치할 수 있다
모바일 반응형 작업만 했다면 웹과 앱 모두 사용할 수 있다 </p>
<h2 id="내가-만든-페이지가-pwa-조건에-만족하는지-확인하기">내가 만든 페이지가 pwa 조건에 만족하는지 확인하기</h2>
<p> 개발자 도구에서 Lighthouse에서 확인이 가능하다 
 <img src="https://velog.velcdn.com/images/wony_yoon/post/fd536e52-0899-4c59-bf91-e13b3aaa15c4/image.png" alt="">
<img src="https://velog.velcdn.com/images/wony_yoon/post/29e0cb81-6c2b-4918-8b3b-196dcb4e94cc/image.png" alt=""></p>
<p>앱개발을 하지않아도 사용할 수 있는 pwa 적용하기 끝!</p>
<hr>
<p>참고 
<a href="https://blog.wishket.com/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%A0%88%EC%8B%9C%EB%B8%8C-%EC%9B%B9-%EC%95%B1pwa%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80/">https://blog.wishket.com/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%A0%88%EC%8B%9C%EB%B8%8C-%EC%9B%B9-%EC%95%B1pwa%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80/</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API">https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[일반화면 전체스크린 만들기]]></title>
            <link>https://velog.io/@wony_yoon/%EC%9D%BC%EB%B0%98%ED%99%94%EB%A9%B4-%EC%A0%84%EC%B2%B4%EC%8A%A4%ED%81%AC%EB%A6%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/%EC%9D%BC%EB%B0%98%ED%99%94%EB%A9%B4-%EC%A0%84%EC%B2%B4%EC%8A%A4%ED%81%AC%EB%A6%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 17 Nov 2022 06:51:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wony_yoon/post/9a48ac01-16e1-4aeb-b1ce-80ea7d6ff422/image.png" alt=""></p>
<p>간혹 브라우저 화면 상단의 페이지 탭과 경로 검색창, 북마크바 / 화면 하단의 메뉴 가 없이 풀스크린으로 보여주어야 하는 상황이 생길 수 있다 </p>
<p>브라우저에서는 F11키를 누르거나 전체 화면 모드를 선택하면 되지만 코드로는 어떻게 해야할지 기록으로 남겨본다 </p>
<pre><code class="language-js">document.documentElement.requestFullscreen();</code></pre>
<p>위의 명령어를 콘솔창에 입력하면 화면이 가득 차게 된다!</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/5e51ead0-0000-48ac-bdfc-ece78529e6cb/image.png" alt=""></p>
<pre><code class="language-js">document.exitFullscreen()</code></pre>
<p>이 명령어를 입력하면 다시 원래의 화면 크기로 돌아온다 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react-hook-form 과 antd select]]></title>
            <link>https://velog.io/@wony_yoon/react-hook-form-%EA%B3%BC-antd-select</link>
            <guid>https://velog.io/@wony_yoon/react-hook-form-%EA%B3%BC-antd-select</guid>
            <pubDate>Fri, 04 Nov 2022 01:55:42 GMT</pubDate>
            <description><![CDATA[<h2 id="✏️-react-hook-form--antd">✏️ react-hook-form + antd</h2>
<p>form을 이용하여 회원가입을 구현중이었는데 맞딱드린 난관 
antd 처럼 제어컴포넌트에서는 register를 아무리써도 값이 넘어오지 않았다</p>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/98e305f2-77e1-4508-a641-2b0256e17968/image.png" alt=""></p>
<p>자꾸 name 타령하길래 화났던 기억,,,</p>
<p>이 빨간창을 맞딱드렸던 그때의 나는 시간이 부족했다 
그래서 select만 hook에서 제외 시키기로 하고 마무리했었다</p>
<p>그치만 앞으로 시작될 프로젝트에선 그럴 수 없기에 해결책을 찾아야했다</p>
<p>역시 구글은 위대하다 앞선 개발자분들께서도 겪은 상황이었는지 다시 찾아보니 글을 찾았고 적용해보았다!</p>
<h2 id="✏️-controller의-발견">✏️ Controller의 발견</h2>
<p>antd나 material ui와 같은 제어 컴포넌트는 hook form 안에서 직접 사용이 어려우니 controller 로 감싸주어야한다는 것을 찾았다</p>
<p>기존의 코드는 이러했는데 </p>
<pre><code class="language-js">&lt;Select {...register(&quot;select&quot;)}&gt;
    &lt;Option value=&quot;horse&quot; name=&quot;horse&quot;&gt;
      말
    &lt;/Option&gt;
    &lt;Option value=&quot;dog&quot; naem=&quot;dog&quot;&gt;
      개
    &lt;/Option&gt;
    &lt;Option value=&quot;pig&quot; name=&quot;pig&quot;&gt;
      돼지
    &lt;/Option&gt;
    &lt;Option value=&quot;rabbit&quot; name=&quot;rabbit&quot;&gt;
      토끼
    &lt;/Option&gt;
&lt;/Select&gt;</code></pre>
<p>구글링 후 아래처럼 적용해보니</p>
<pre><code class="language-js">&lt;Controller
    name=&quot;select&quot;
    control={control}
    render={({ field }) =&gt; (
          &lt;Select defaultValue={&quot;dog&quot;} {...field}&gt;
            &lt;Option value=&quot;horse&quot; name=&quot;horse&quot;&gt;
              말
            &lt;/Option&gt;
            &lt;Option value=&quot;dog&quot; naem=&quot;dog&quot;&gt;
              개
            &lt;/Option&gt;
            &lt;Option value=&quot;pig&quot; name=&quot;pig&quot;&gt;
              돼지
            &lt;/Option&gt;
            &lt;Option value=&quot;rabbit&quot; name=&quot;rabbit&quot;&gt;
              토끼
            &lt;/Option&gt;
        &lt;/Select&gt;
    )}
/&gt;
</code></pre>
<p><img src="https://velog.velcdn.com/images/wony_yoon/post/57637e84-257f-4a5e-8849-7343bf4a0279/image.png" alt=""></p>
<p>값이 아주 잘 찍힌다! </p>
<p>해결했으니 지난주에 적용못한 곳에도 적용 해봐야겠다 룰루🎶</p>
<hr>
<p>참고
<a href="https://travis.media/react-hook-form-controller-examples/#20211019-antd-select">https://travis.media/react-hook-form-controller-examples/#20211019-antd-select</a>
<a href="https://react-hook-form.com/api/usecontroller/controller">https://react-hook-form.com/api/usecontroller/controller</a>
<a href="https://wonillism.tistory.com/279">https://wonillism.tistory.com/279</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[배열] 시작날짜와 끝날짜 사이에 비어있는 날짜 채워주기]]></title>
            <link>https://velog.io/@wony_yoon/%EB%B0%B0%EC%97%B4-%EC%8B%9C%EC%9E%91%EB%82%A0%EC%A7%9C%EC%99%80-%EB%81%9D%EB%82%A0%EC%A7%9C-%EC%82%AC%EC%9D%B4%EC%97%90-%EB%B9%84%EC%96%B4%EC%9E%88%EB%8A%94-%EB%82%A0%EC%A7%9C-%EC%B1%84%EC%9B%8C%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@wony_yoon/%EB%B0%B0%EC%97%B4-%EC%8B%9C%EC%9E%91%EB%82%A0%EC%A7%9C%EC%99%80-%EB%81%9D%EB%82%A0%EC%A7%9C-%EC%82%AC%EC%9D%B4%EC%97%90-%EB%B9%84%EC%96%B4%EC%9E%88%EB%8A%94-%EB%82%A0%EC%A7%9C-%EC%B1%84%EC%9B%8C%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Thu, 03 Nov 2022 07:58:30 GMT</pubDate>
            <description><![CDATA[<p>이 제목만 보고 내용이 이해가는 사람이 있을까 싶지만 내가 하고 싶었던 기능이었다</p>
<p>시작날짜와 끝날짜는 유저가 데이트피커로 선택한 날짜고 그 사이에 있는 날짜별 데이터들을 받아왔다
받아오고 보니 DB에 값이 없는 날짜는 배열안에 존재하지 않았다 
하지만 나는 DB에 존재하지 않는 날은 0으로 보여줘야했기에 배열안에 없는 날짜를 추가해주어야 했다 </p>
<p>목적 파악 완료!</p>
<pre><code class="language-js">export default function getDatesStartToLast(
  startDate: string,
  endDate: string
) {
  const result = [];
  const curDate = new Date(startDate);
  while (curDate &lt;= new Date(endDate)) {
    const date = curDate.toISOString().split(&quot;T&quot;)[0];
    result.push(date);
    curDate.setDate(curDate.getDate() + 1);
  }
  return result;
}</code></pre>
<p>내가 만든 이 함수는 시작 날짜부터 끝날짜까지 사이의 모든 날짜를 배열에 담아 return하는 함수이다</p>
<p>필요하다면 가져가서 맘껏 쓰시길,,,ㅎㅎ</p>
<p>위의 함수에서 return된 배열과 백엔드에서 받아온 배열을 비교하여 없는 날짜는 넣어주고 그 값은 0으로 처리하면 마무리 되는 것이었다</p>
<pre><code class="language-js">  const arrFunction = (dateArr: string[], getDataArr: string[]) =&gt; {
    const arr: string[] = [];

    dateArr.forEach((el) =&gt; {
      const name = data ? getDataArr.indexOf(el) : [];
      if (name === -1) {
        arr.push(el);
      }
    });
    const newArr = arr
      .map((el, i) =&gt; {
        return { cntDate: el, exportCnt: 0, studyCnt: 0 };
      })
      .concat(data || [])
      // @ts-ignore
      .sort((a, b) =&gt; new Date(a.cntDate) - new Date(b.cntDate));

    return newArr;
  };
</code></pre>
<p><code>indexOf</code>를 통해 없는 날짜는 새배열에 넣어주고 <code>map</code>을 통해 객체의 cntDate에 없는 날짜 추가 와 함께 exportCnt와 studyCnt는 0이라고 넣어주었다  </p>
<p>그 다음에 기존의 data와 <code>concat</code>을 통해 합쳐주었고 <code>sort</code>를 통해 날짜순으로 정렬를 해주면 완성..!</p>
<p>말하고 보니 더 간단하게 구현할 수 있지 않을까 했지만 그래도 머리를 많이썼던 로직이었다 </p>
]]></description>
        </item>
    </channel>
</rss>