<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jju.log</title>
        <link>https://velog.io/</link>
        <description>Review the Record⭐</description>
        <lastBuildDate>Thu, 02 May 2024 07:36:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jju.log</title>
            <url>https://velog.velcdn.com/images/jjya_3562/profile/7337c5f7-10cb-4508-81da-c104f953766a/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jju.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jjya_3562" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 해시 전화번호 목록]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%B4%EC%8B%9C-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EB%AA%A9%EB%A1%9D</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%B4%EC%8B%9C-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EB%AA%A9%EB%A1%9D</guid>
            <pubDate>Thu, 02 May 2024 07:36:41 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-설명">문제 설명</h2>
<p>전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.
전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.</p>
<ul>
<li>구조대 : 119</li>
<li>박준영 : 97 674 223</li>
<li>지영석 : 11 9552 4421</li>
</ul>
<p>전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요</p>
<h2 id="제한-사항">제한 사항</h2>
<ul>
<li>phone_book의 길이는 1 이상 1,000,000 이하입니다.<ul>
<li>각 전화번호의 길이는 1 이상 20 이하입니다.</li>
<li>같은 전화번호가 중복해서 들어있지 않습니다.</li>
</ul>
</li>
</ul>
<h2 id="입출력-예제">입출력 예제</h2>
<table>
<thead>
<tr>
<th>phone_book</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[&quot;119&quot;, &quot;97674223&quot;, &quot;1195524421&quot;]</td>
<td>false</td>
</tr>
<tr>
<td>[&quot;123&quot;,&quot;456&quot;,&quot;789&quot;]</td>
<td>true</td>
</tr>
<tr>
<td>[&quot;12&quot;,&quot;123&quot;,&quot;1235&quot;,&quot;567&quot;,&quot;88&quot;]</td>
<td>false</td>
</tr>
</tbody></table>
<p>** 문제 단순화 하기 **
어떤 번호가 다른 번호의 시작과동일하면 된다!</p>
<p>처음 생각했던 방법은</p>
<ol>
<li>순서를 정렬 
Arrays.sort();</li>
</ol>
<p>2.제일 작은 수인 [0] 번의 배열과 전부 비교해보기
.substring(int startIndex,int endIndex);
-&gt; startIndex(포함)부터 endIndex(불포함)까지의 문자열을 리턴
ex) str.substring(2, 4) : startIndex 2부터 endIndex 4 이전까지의 문자열을 잘라서 리턴</p>
<pre><code class="language-java">import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        boolean answer = true;

        Arrays.sort(phone_book);

        for(int i =1;i &lt; phone_book.length;i++){
            if(phone_book[0].equals(phone_book[i].substring(0, phone_book[0].length())))
            answer = false;
            break;
        }
        return answer;
    }
}</code></pre>
<p>이렇게 하니까 런타임 에러가 발생했다...</p>
<h3 id="런타임-에러가-발생할-수-있는-원인">런타임 에러가 발생할 수 있는 원인!</h3>
<ul>
<li>배열에 할당된 크기를 넘어서 접근했을 때</li>
<li>전역 배열의 크기가 메모리 제한을 초과할 때</li>
<li>지역 배열의 크기가 스택 크기 제한을 넘어갈 때</li>
<li>0으로 나눌 떄</li>
<li>라이브러리에서 예외를 발생시켰을 때</li>
<li>재귀 호출이 너무 깊어질 때</li>
<li>이미 해제된 메모리를 또 참조할 때</li>
<li>프로그램(main 함수)이 0이 아닌 수를 반환했을 때</li>
</ul>
<p>잘 모르겠다..
먼가 코드가 잘못된거 같다</p>
<hr>
<p>그럼? 다시 처음으로 돌아와서
Hash문제니까 hash를 사용해봐야지?</p>
<ol>
<li><p>배열의 모든 수를 HashMap에 넣기
HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(Key,Value);</p>
</li>
<li><p>접두어가 있는지 확인하기
HashMap.containsKey(String);</p>
</li>
</ol>
<p>-&gt; String 이라는 key가 현재 HashMap에 있는지 확인</p>
<pre><code class="language-java">import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        boolean answer = true;

        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

        for(int i=0;i &lt; phone_book.length;i++){
            map.put(phone_book[i], i);
        }

        for(int i=0;i&lt;phone_book.length;i++){
            for(int j = 0;j &lt; phone_book[i].length();j++){
                if(map.containsKey(phone_book[i].substring(0,j))){
                    answer=false;
                }
            }

        }

        return answer;
    }
}</code></pre>
<p>그렇다면 여기서! 
value 가 필요없는데 HashSet을 사용할순 없을까? 라는 의문이 든다.
두가지의 차이점이 무엇일까?</p>
<h3 id="hashmap-vs-hashset">HashMap vs HashSet</h3>
<table>
<thead>
<tr>
<th></th>
<th>HashMap</th>
<th>HashSet</th>
</tr>
</thead>
<tbody><tr>
<td>정의</td>
<td>Map interface 구현체</td>
<td>Set interface 구현체</td>
</tr>
<tr>
<td>데이터 저장형태</td>
<td>key-value<br> value들이 key에 mapping</td>
<td>객체 그 자체를 저장<br> 내부구현코드에서 필드로 선언한 객체(dummy 객체)를 value 값으로 사용</td>
</tr>
<tr>
<td>데이터 삽입 방법</td>
<td>put() <br> key-value형태로 저장 1개의 객체 생성</td>
<td>add() <br> 객체 자체를 저장하고, 내부적으로 HashMap을 사용 2개의 객체가 삽입 연산동안 생성</td>
</tr>
<tr>
<td>중복여부</td>
<td>key 중복 허용x<br> vaule중복 허용 o</td>
<td>중복혀용 x</td>
</tr>
<tr>
<td>null 허용여부</td>
<td>key - 단 하나의 null<br> vaule - 여러개의 null</td>
<td>단 하나의 null값</td>
</tr>
<tr>
<td>성능</td>
<td>HashSet보다 빠름</td>
<td>오직 객체만 저장 가능해 HashMap보다 느림</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 완전탐색 모의고사]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%99%84%EC%A0%84%ED%83%90%EC%83%89-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%99%84%EC%A0%84%ED%83%90%EC%83%89-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Tue, 30 Apr 2024 07:48:28 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-설명">문제 설명</h2>
<p>수포자는 수학을 포기한 사람의 준말입니다. 수포자 삼인방은 모의고사에 수학 문제를 전부 찍으려 합니다. 수포자는 1번 문제부터 마지막 문제까지 다음과 같이 찍습니다.</p>
<p>1번 수포자가 찍는 방식: 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...
2번 수포자가 찍는 방식: 2, 1, 2, 3, 2, 4, 2, 5, 2, 1, 2, 3, 2, 4, 2, 5, ...
3번 수포자가 찍는 방식: 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, ...</p>
<p>1번 문제부터 마지막 문제까지의 정답이 순서대로 들은 배열 answers가 주어졌을 때, 가장 많은 문제를 맞힌 사람이 누구인지 배열에 담아 return 하도록 solution 함수를 작성해주세요.</p>
<h2 id="제한-조건">제한 조건</h2>
<ul>
<li>시험은 최대 10,000 문제로 구성되어있습니다.</li>
<li>문제의 정답은 1, 2, 3, 4, 5중 하나입니다.</li>
<li>가장 높은 점수를 받은 사람이 여럿일 경우, return하는 값을 오름차순 정렬해주세요.</li>
</ul>
<h2 id="입출력-예">입출력 예</h2>
<table>
<thead>
<tr>
<th>answers</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[1,2,3,4,5]</td>
<td>[1]</td>
</tr>
<tr>
<td>[1,3,2,4,2]</td>
<td>[1,2,3]</td>
</tr>
</tbody></table>
<h2 id="solution">Solution</h2>
<p>1, 2, 3, 4, 5  ==&gt; 1 ~ 5 순서 반복
2, 1, 2, 3, 2, 4, 2, 5  ==&gt; 1 ~ 8 순서 반복
3, 3, 1, 1, 2, 2, 4, 4, 5, 5  ==&gt; 1 ~ 10 순서 반복
반복</p>
<pre><code class="language-java">import java.util.*;

class Solution {
    public int[] solution(int[] answers) {
        int[] answer =new int[3];
        // 나머지 연산자 이용        
        int[][] math = {
            {1, 2, 3, 4, 5},
            {2, 1, 2, 3, 2, 4, 2, 5},
            {3, 3, 1, 1, 2, 2, 4, 4, 5, 5} 
        };
        // 높은점수 받은 사람을 담을 배열
        int [] count = new int[math.length];
        int max =0;

        for(int i = 0; i &lt; answers.length; i++){
            for(int j=0;j &lt; math.length; j++){
                if(answers[i] == math[j][i % math[j].length]){
                    count[j] ++;
                    max = Math.max(max, count[j]);
                }

            }
        }

        System.out.println(Arrays.toString(count)+&quot;,&quot;+max);

        for(int i = 0; i &lt; 3; i++){
            if(count[i] == max){
                answer[i] = i + 1;
            }
        }

        return Arrays.stream(answer).filter(i -&gt; i != 0).toArray();

    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 정렬 가장큰수 ]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A0%95%EB%A0%AC-%EA%B0%80%EC%9E%A5%ED%81%B0%EC%88%98</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A0%95%EB%A0%AC-%EA%B0%80%EC%9E%A5%ED%81%B0%EC%88%98</guid>
            <pubDate>Tue, 30 Apr 2024 05:43:19 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-설명">문제 설명</h2>
<p>0 또는 양의 정수가 주어졌을 때, 정수를 이어 붙여 만들 수 있는 가장 큰 수를 알아내 주세요.</p>
<p>예를 들어, 주어진 정수가 [6, 10, 2]라면 [6102, 6210, 1062, 1026, 2610, 2106]를 만들 수 있고, 이중 가장 큰 수는 6210입니다.</p>
<p>0 또는 양의 정수가 담긴 배열 numbers가 매개변수로 주어질 때, <strong>순서를 재배치</strong>하여 만들 수 있는 가장 큰 수를 <strong>문자열로 바꾸어</strong> return 하도록 solution 함수를 작성해주세요.</p>
<h2 id="제한-사항">제한 사항</h2>
<ul>
<li>numbers의 길이는 1 이상 100,000 이하입니다.</li>
<li>numbers의 원소는 0 이상 1,000 이하입니다.</li>
<li>정답이 너무 클 수 있으니 문자열로 바꾸어 return 합니다.</li>
</ul>
<h2 id="입출력-예">입출력 예</h2>
<table>
<thead>
<tr>
<th>numbers</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[6, 10, 2]</td>
<td>&quot;6210&quot;</td>
</tr>
<tr>
<td>[3, 30, 34, 5, 9]</td>
<td>&quot;9534330&quot;</td>
</tr>
</tbody></table>
<h2 id="solution">solution</h2>
<h3 id="comparator를-직접-구현하여-string끼리-값을-비교할-수-있는지를-묻는-문제">Comparator를 직접 구현하여 String끼리 값을 비교할 수 있는지를 묻는 문제</h3>
<ol>
<li>하나의 숫자끼리 비교해서 정렬을 하는것은 소용없음
두 숫자를 합쳐서 비교해서 정렬을 해야함</li>
<li>숫자 비교시 문자열로 먼저 변환후 비교해야함</li>
<li>비교한 결과값으로 순서 재배치</li>
</ol>
<p>** =&gt; Comparator 의 compareTo() 이용 **
오름차순으로 정렬시 (o2 + o1).compareTo(o1 + o2) 
내림차순으로 정렬시 (o1 + o2).compareTo(o1 + o2) </p>
<h3 id="comparator의-compareto">Comparator의 compareTo</h3>
<p> : 두개의 값을 비교하여 int 값으로 반환해주는 함수 </p>
<ul>
<li><strong>숫자의 비교 같은 경우</strong> 단순히 크다(1), 같다(0), 작다(-1) 의 관한 결과값을 리턴해주는 반면 </li>
<li><strong>문자열의 비교 같은 경우</strong> 같다(0), 그 외 양수/음수값 같이 참 재미난 결과를 반환해준다.</li>
</ul>
<pre><code class="language-java">import java.util.Arrays;
class Solution {
    public String solution(int[] numbers) {
        String answer = &quot;&quot;;
        // 문자열로 바꿔서 담아줄 배열 선언및 초기화
        String [] arr = new String[numbers.length]

        // 배열 복사해서 넣어주기
        for(int i=0;i &lt; numbers.length;i++){
            // 문자열로 변환해서 복사
            arr[i] = String.vauleOf(numbers[i]);  
        }
        // =&gt; 두 배열의 숫자가 더한 숫자가 아닌 문자열로 보여줘야하기 때문에 문자열 배열로 복사함

        // 정렬하기 - 비교후 내림차순으로 정렬
        Arrays.sort(arr, (o1,o2) -&gt; (o2 + o1).compareTo(o1 + o2));

        //[0,0,0]의 배열의 경우 - 예외 처리 해주기
        if(arr[0].equals(&quot;0&quot;)){
            return 0 
        }

        //문자열 연결 : String.join
        answer = String.join(&quot;&quot;,arr);


        return answer;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 해시 의상]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%B4%EC%8B%9C-%EC%9D%98%EC%83%81</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%B4%EC%8B%9C-%EC%9D%98%EC%83%81</guid>
            <pubDate>Mon, 29 Apr 2024 12:28:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jjya_3562/post/27c63083-430e-4c67-9aaf-9914dc84c1cd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jjya_3562/post/e6a1e704-eafd-4bba-acaa-199bf3fce710/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jjya_3562/post/f503d7e1-5e20-4784-b37d-8fff3f0a4754/image.png" alt=""></p>
<pre><code class="language-JAVA">import java.util.HashMap;

class Solution {
    public int solution(String[][] clothes) {
        int answer = 1;
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

        for(String[] clothe : clothes){
            String key = clothe[1];
            map.put(key, map.getOrDefault(key,0)+1);
        }

        for(String key : map.keySet()){
            answer *= (map.get(key) + 1);
        }

        answer = answer -1;


        return answer;
    }
}


// 의상이름 + 의상종류
//HashMap? kwy +value </code></pre>
<ul>
<li>getOrDefault(Object key, V DefaultValue)</li>
<li>keySet() : Map의 전체 key를 꺼냄</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript]]></title>
            <link>https://velog.io/@jjya_3562/TypeScript</link>
            <guid>https://velog.io/@jjya_3562/TypeScript</guid>
            <pubDate>Wed, 10 Apr 2024 05:15:54 GMT</pubDate>
            <description><![CDATA[<h2 id="typescript-설치">TypeScript 설치</h2>
<pre><code class="language-javaacript">npm install typescript @type/node @type/react @type/react-router-dom @type/jest @type/react-dom</code></pre>
<ul>
<li>js 파일 → .ts </li>
<li>jsx 파일→ .tsx</li>
</ul>
<h2 id="javascript-vs-typescript">javascript vs TypeScript</h2>
<ul>
<li>javascript(동적언어) : 런타임에 타입 결정 / 오류 발견</li>
<li>Java, Typescript(정적언어) : 컴파일 타임에 타입 결정 / 오류 발견</li>
</ul>
<pre><code class="language-javascript">function add (num1 : number, num2 : number){
    console.log(num1+num2);
}
add(1,2);



function showItems(arr : number[] or : Array&lt;number&gt;){
    arr.forEach(item =&gt;{
        console.log(item)
    })
}
showItems([1,2,3]);</code></pre>
<h3 id="튜플">튜플</h3>
<pre><code class="language-javascript">let b:[string, numbder];
b[&#39;s&#39;, 1];


//void, never
function sayHello():void{
    console.log(&#39;hello&#39;);
}

//항상 에러를 반환하거나 영원히 끝나지 않는 함수에서 사용
function error():never{
    throe new Error();
}

function loof():never{
    while(true){

    }
}</code></pre>
<pre><code class="language-javascript">type Score = &#39;A&#39; | &#39;B&#39;;

interface User{
    name : string,
    age : number
    readonly gender? : string 
    [grade:number] : Score
}

let user : User = {
    name=&#39;ss&#39;,
    age= 2,
    1: &#39;A&#39;,
    2: &#39;B&#39;
}

console.log(user.name)


//함수 정의
interface Add(){
    (num1 : number, num2 : number) : number
}

const add : Add = function(x,y){
    return x+y
}

add(10,20)


function arr(...num : number[]){
=&gt; 전달받은 매게변수를 배열로 나타냄
    return num.reduce((result, num) =&gt; result + num, 0)

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React state, useEffect]]></title>
            <link>https://velog.io/@jjya_3562/React-state-useEffect</link>
            <guid>https://velog.io/@jjya_3562/React-state-useEffect</guid>
            <pubDate>Wed, 10 Apr 2024 05:12:08 GMT</pubDate>
            <description><![CDATA[<h2 id="state">state</h2>
<ul>
<li>prop : 컨퍼넌트를 사용하는 외부자를 위한 데이터</li>
<li>state : 컨퍼넌트를 만드는 내부자를 위한 데이터<pre><code class="language-javascript">// 상태 생성시
</code></pre>
</li>
</ul>
<p>// **  원시데이터타입
Const [value, setValue] = useState(PRIMITIVE);
string number boolean</p>
<p>// **  범객체
Const [value, setValue] = useState(Object);
object, array</p>
<p>// 처리방벙이 달라짐 = 데이터 복제
newValue = {...vaue}
newValue변경 // 복제본 바꿈
setValue(newValue) 
// 컴퍼넌트 다시실행</p>
<p>newValue = [...vaue] //복제
newValue변경 // 복제본 바꿈
setValue(newValue)</p>
<p>// setValue시 value값이 변경이 있는지 확인함
// 변경시에만 컴퍼넌트 다시 렌더링 </p>
<pre><code>
## useEffect
- 상태값이 바뀌었을때 동작하는 함수 작성가능
- 첫번째 매개변수 함수
- 두번째 매개변수 배열 - 의존성배열(이 배열이 변경될때만 작동함)
```javascript
useEffect(() =&gt; {
    fetch(&#39;http://api경로&#39;)
    //API비동기 통신을 위해 
    .then(res=&gt;{
        return res.json();
        // 실제 json은 아니고 response응답임
        // json 메소드를 이용해서 json응답 반환
    }) 
}, []);
// 빈배열넣어줌
// 상태값과 무관하게 딱한번만 호출되게 하기위</code></pre><ul>
<li><p>랜더링결과가 돔에 반영된 직후 작동함
—&gt; API호출 의 목적으로 사용
—&gt; 랜더링이 완료된후 최초의 한번만 작동하게 하면 됨</p>
</li>
<li><p>두번째 매개변후 배열을 빈 배열로 넣어줌</p>
</li>
</ul>
<br>


<h2 id="json-server-이용">JSON SERVER 이용</h2>
<pre><code class="language-javascript">//json-server 글로벌(-g) 설치
npm install -g json-server

//json-server 3001번 포트에서 실행
json-server --watch ./src/db/data.json --port 3001</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Components와 Props, 이벤트]]></title>
            <link>https://velog.io/@jjya_3562/React-Components%EC%99%80-Props</link>
            <guid>https://velog.io/@jjya_3562/React-Components%EC%99%80-Props</guid>
            <pubDate>Wed, 10 Apr 2024 05:05:15 GMT</pubDate>
            <description><![CDATA[<p><a href="https://ko.legacy.reactjs.org/docs/components-and-props.html">공식 문서 바로가기</a></p>
<h2 id="components">Components</h2>
<ul>
<li>사용자 정의 태그 사용(대문자로 사용) → 함수를 정의
: 함수의 형태로 사용 (생산성 증가)<pre><code class="language-javascript">function Header(){
   return &lt;header&gt;header&lt;/header&gt;
}
// Header의 함수를 정의
// header는 태그 

</code></pre>
</li>
</ul>
<p>function App(){
    <Header></Header>
    // 사용자 정의 태그 (컴퍼넌트) Header로 사용
}</p>
<pre><code>

## props
- 속성
```javascript
function Header(props){ // 파라미터로 props받아줌
     return &lt;header&gt;{props.title}&lt;/header&gt;
}  

function App(){
    &lt;Header title=&quot;속성 내용&quot;&gt;&lt;/Header&gt;
    // 속성 내용 작성
}</code></pre><pre><code class="language-javascript">function Nav (props){ //속성 받아옴
    const lis = []
    //lis 라는 배열로 뿌려줄수 있게 해주기
    for(let i = 0;i &lt;props.topics.length; i++){
        const t = props.topics[i];
        lis.push(&lt;li key={t.id}&gt;&lt;a href={&#39;/read/&#39;+t.id}&gt;{t.title}&lt;/a&gt;&lt;li&gt;)
    }

    return &lt;nav&gt;
    &lt;ol&gt;
        &lt;li&gt;&lt;a href=&quot;#&quot;&gt;&lt;/a&gt;&lt;/li&gt;
        //위의 구조를 반복해서 가져올수 있게 코드를 짜야함

        {lis} //lis 라는 함수를 불러올수 있게 하기
    &lt;/ol&gt;
    &lt;/nav&gt;
}


function App(){
    const topics = [ //배열로 받아오기
        //속성값들을 배열 코드로 작성
        {id: 1, title:&quot;html&quot; , body:&quot;html...&quot;},
        {id: 2, title:&quot;css&quot; , body:&quot;css...&quot;},
        {id: 3, title:&quot;js&quot; , body:&quot;js...&quot;}
    ]

    &lt;Nav topics = topics&gt;&lt;/Nav&gt;
    // 이렇게 작성할시에는 topics이라는 문자열을 받아옴
    &lt;Nav topics={topics}&gt;&lt;/Nav&gt;

}
</code></pre>
<h2 id="이벤트">이벤트</h2>
<pre><code class="language-javascript">function Header(props){ //파라미터
  return &lt;header&gt;
  &lt;a href=&quot;/&quot; onClick={(event)=&gt;{
    event.preventDefault();
    props.onChangeMode();
  }}&gt;{props.title}&lt;/a&gt;
  &lt;/header&gt;
}

function App() {
return (    
    &lt;div&gt;
      &lt;Header title=&quot;web1&quot; onChangeMode={()=&gt;{
        alert(&quot;header&quot;);
      }}&gt;&lt;/Header&gt; 
)
}
// props(속성) onChangeMode로 입력
// 컨퍼넌트에 공통으로 작성되어야할 이벤트 작성
// 속성에 있는 onChangeMode 불러와서 사용가능</code></pre>
<br>

<ul>
<li>참고 <pre><code>function(){}
회살표 함수 : () =&gt; {}</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 설치, 수정, 배포]]></title>
            <link>https://velog.io/@jjya_3562/React-%EC%84%A4%EC%B9%98-%EC%88%98%EC%A0%95-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@jjya_3562/React-%EC%84%A4%EC%B9%98-%EC%88%98%EC%A0%95-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Wed, 10 Apr 2024 05:00:13 GMT</pubDate>
            <description><![CDATA[<h2 id="react-설치">React 설치</h2>
<ul>
<li>react 설치전 폴더만들어줌( 폴더이름에 대문자가 있으면 설치가 안됨)</li>
<li>node.js 설치후, vscode에서 터미널 열어줌</li>
<li>터미널에 commond prompt 에서 설치 코드 작성</li>
</ul>
<pre><code class="language-javascript">npx create-react-app .

// powoershell에서 설치가 잘 안될수도 있음
// . : 현재폴더에 생성</code></pre>
<h2 id="react-수정">React 수정</h2>
<ul>
<li>index.js 입구 파일 → 제일 먼저 index.js파일을 읽어드림 (App을 불러옴)</li>
<li>App.js에서 수정해서 사용하면됨</li>
<li>App.css 스타일 지정해주면됨</li>
<li>public 안에 전체 html구조가 있음 → index.html
<img src="https://velog.velcdn.com/images/jjya_3562/post/2835161e-46d9-4d33-b424-b16cbb150214/image.png" alt=""></li>
</ul>
<h2 id="react-배포">React 배포</h2>
<ul>
<li><p>서비스에 최적화 되어있는 배포본을 만들수 있을까?</p>
</li>
<li><p>터미널 켜줌 (실행하고 있는 명령들이 있을경우 : ctrl+c )</p>
<pre><code class="language-javascript">// 배포판 만들어주기
npm run build</code></pre>
</li>
<li><p>build 파일 생성 - 공백없음(용량의 최소한을 위해)</p>
</li>
<li><p>serve 라는 웹서버를 쓰는걸 권유 (node.js로 만들어진 애플리케이션 npx)</p>
<pre><code class="language-javascript">// 웹서버 실행 (3000번포트 사용)
npx serve -s build</code></pre>
</li>
<li><p>라이브 실행</p>
<pre><code>npm start</code></pre><p><a href="http://localhost:3000/">http://localhost:3000/</a> </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React란?]]></title>
            <link>https://velog.io/@jjya_3562/React</link>
            <guid>https://velog.io/@jjya_3562/React</guid>
            <pubDate>Wed, 10 Apr 2024 04:54:02 GMT</pubDate>
            <description><![CDATA[<h2 id="react란">React란?</h2>
<blockquote>
<p>React는 웹 프레임워크로, 자바스크립트 라이브러리의 하나로서 사용자 인터페이스를 만들기 위한 JavaScript 라이브러리</p>
</blockquote>
<p><a href="https://ko.legacy.reactjs.org/">REACT 공식문서 참고</a></p>
<ul>
  <li> <h3>Node.js</h3>
    <ul>
      <li>    REACT 사용시 Node.js 와 함께 사용</li>
    </ul>
  </li>  

<li> <h3>JSX</h3>
  <ul>
    <li>JavaScript를 확장한 문법</li>
    <li>자바스크립트 코드와 html 코드를 함께 사용할 수 있어 편리함</li>
    <li>UI가 어떻게 생겨야 하는지 설명하기 위해 React와 함께 사용할 것을 권장</li>
    <li><a href="https://ko.legacy.reactjs.org/docs/introducing-jsx.html">REACT JSX 공식문서 참고</a></li>
  </ul>
 </li>
    </ul>    
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 정렬 K번째수]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A0%95%EB%A0%AC-K%EB%B2%88%EC%A7%B8%EC%88%98</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A0%95%EB%A0%AC-K%EB%B2%88%EC%A7%B8%EC%88%98</guid>
            <pubDate>Sun, 31 Mar 2024 08:22:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jjya_3562/post/00709c63-0dff-475c-9050-1abeca338c4f/image.png" alt=""></p>
<hr>
<h3 id="문제풀이-순서">문제풀이 순서</h3>
<ol>
<li>answer의 배열의 길이 == commands의 배열의 길이</li>
<li>array배열의 i번째 부터 k번째 까지 자른 배열을 저장할 temp 배열을 만든다. </li>
<li>temp 배열을 정렬  </li>
<li>commands[i][2] - 1 번째 temp숫자를 answer에 순서대로 넣어줌</li>
</ol>
<hr>
<h3 id="정렬">정렬</h3>
<ol>
<li>특정 범위 배열을 복사 <strong>Arrays.copyOfRange()</strong></li>
<li>배열 정렬 <strong>Arrays.sort()</strong> </li>
</ol>
<hr>
<h3 id="코드">코드</h3>
<pre><code class="language-java">import java.util.Arrays;

class Solution {
    public int[] solution(int[] array, int[][] commands) {
        int[] answer = new int[commands.length];

        for(int i = 0;i &lt; commands.length;i++){
            int[] tmep = Arrays.copyOfRange(array,commands[i][0]-1, commands[i][1]);
            Arrays.sort(tmep);
            answer[i] = tmep[commands[i][2] - 1];
        }

        return answer;
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 스택/큐 같은 숫자는 싫어]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%83%9D%ED%81%90-%EA%B0%99%EC%9D%80-%EC%88%AB%EC%9E%90%EB%8A%94-%EC%8B%AB%EC%96%B4</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%83%9D%ED%81%90-%EA%B0%99%EC%9D%80-%EC%88%AB%EC%9E%90%EB%8A%94-%EC%8B%AB%EC%96%B4</guid>
            <pubDate>Sat, 30 Mar 2024 16:48:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jjya_3562/post/7c6e9d92-5690-49de-ab77-ccbf8bb122e5/image.png" alt=""></p>
<h3 id="문제풀이-순서">문제풀이 순서</h3>
<ol>
<li><p>반복문을 돌면서 배열에 있는 숫자 Stack에 삽입
 1-1. 맨 처음 숫자는 무조건 담기
 1-2. 다음부터는 스택에 담겨있는 숫자와 비교해서 같지 않을경우에만 넣기</p>
</li>
<li><p>answer의 사이즈 Stack에 사이즈로 만들기</p>
</li>
<li><p>Stack에 있는 숫자 꺼내기
 여기서 주의! Stack은 후입선출(LIFO : Last In First Out) 방식으로 자료를 처리함 (arr의 배열의 순서를 유지 해야함으로 역순으로 담아야함)</p>
</li>
</ol>
<hr>
<h3 id="stack">Stack</h3>
<pre><code class="language-java">Stack&lt;T&gt; 스택 이름 = new Stack&lt;&gt;();</code></pre>
<ul>
<li>add() : 값을 추가</li>
<li>push() : 데이터 스택에 추가, 해당값 반환</li>
<li>pop() : 값 하나씩 빼낼수 있음
스택에서 값이 제거 되면서 동시에 값 반환</li>
<li>clear() : 값 모두 제거 / 반환되는 값이 없음</li>
<li>peak() : 스택의 마지막 요소 반환, 스택에는 변화를 주지 않음
스택이 비어있을경우, NoSuchElementException 예외 발생</li>
</ul>
<hr>
<h3 id="코드">코드</h3>
<pre><code class="language-java">import java.util.*;

public class Solution {
    public int[] solution(int []arr) {
        int[] answer;

        Stack&lt;Integer&gt; st = new Stack&lt;&gt;();

        for(int i=0; i &lt; arr.length; i++){

            if(i == 0){ //처음 무조건 넣음
                st.push(arr[i]);
            }else if(st.peek() != arr[i]){ //스택의 최상단값(peek)이 같지 않을경우에만 삽입
                st.push(arr[i]);
            } 
         }

        answer = new int[st.size()];

        // 역순으로 담아야함 - pop해서 꺼내기
        for(int i = st.size()-1; i &gt;=0; i--){
            answer[i] = st.pop();
        }


        System.out.println(&quot;Hello Java&quot;); 
        return answer;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 알고리즘] 해시 폰켓몬]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%B4%EC%8B%9C-%ED%8F%B0%EC%BC%93%EB%AA%AC</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%B4%EC%8B%9C-%ED%8F%B0%EC%BC%93%EB%AA%AC</guid>
            <pubDate>Mon, 25 Mar 2024 17:04:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jjya_3562/post/bc5c18b4-f9ff-4b78-9b8f-db25cf9dcc7a/image.png" alt=""></p>
<h4 id="가져갈수-있는-최대-폰겟몬의-갯수----max--2">가져갈수 있는 최대 폰겟몬의 갯수  =  max / 2</h4>
<h3 id="문제-풀이-순서">문제 풀이 순서</h3>
<ol>
<li>가져갈수 있는 최대 폰켓몬 수를 구한다</li>
<li>hashSet을 이용해 중복제거</li>
<li>중복 제거한 set이 max보다 크면 max / 작으면 set의 사이즈 리턴</li>
</ol>
<h3 id="코드">코드</h3>
<pre><code class="language-java">import java.util.HashSet;
    class Solution {
        public int solution(int[] nums) {
            int answer = nums.length / 2;

            HashSet&lt;Integer&gt; numSet = new HashSet&lt;&gt;();

            for(int num : nums){
                numSet.add(num);
            }

            if(numSet.size() &lt; answer){
                return answer;
            }else {
                return numSet.size();
            }

        }


    }
</code></pre>
<h3 id="hashset이란">HashSet이란?</h3>
<p>HashSet은 Set 인터페이스에서 지원하는 구현 클래스이다. 때문에 Set의 성질을 그대로 상속받는 다는 것이 특징이다.</p>
<h3 id="hashset의-성질">HashSet의 성질</h3>
<ol>
<li>HashSet은 중복된 값을 허용하지 않습니다.</li>
<li>List 등과는 다르게 저장한 순서가 보장되지 않습니다.</li>
<li>null을 값으로 허용합니다.</li>
</ol>
<hr>
<h3 id="참고">참고</h3>
<p><a href="https://velog.io/@acacia__u/hashSet">https://velog.io/@acacia__u/hashSet</a>
<a href="https://velog.io/@ajufresh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%8F%B0%EC%BC%93%EB%AA%AC-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4-Java">https://velog.io/@ajufresh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%8F%B0%EC%BC%93%EB%AA%AC-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4-Java</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 코딩테스트] 각도기]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%81%EB%8F%84</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%81%EB%8F%84</guid>
            <pubDate>Fri, 22 Mar 2024 09:54:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jjya_3562/post/232b33da-4c8d-4462-b866-299c88624324/image.png" alt=""></p>
<h3 id="나의-문제풀이">나의 문제풀이</h3>
<pre><code class="language-java">class Solution {
    public int solution(int angle) {
        int answer = 0;
        if(angle &gt; 0 &amp;&amp; angle &lt;= 180){
            if(angle &gt; 0 &amp;&amp; angle &lt;90){
                answer = 1;
            }else if(angle == 90){
                answer = 2;
            }else if(angle &gt; 90 &amp;&amp; angle &lt; 180){
                answer = 3;
            }else{
                answer = 4;
            }
        }

        return answer;
    }
}</code></pre>
<h3 id="삼항-연산자-리팩토링">삼항 연산자 리팩토링</h3>
<pre><code class="language-java">class Solution {
    public int solution(int angle) {
        return angle == 180 ? 4 : angle &lt; 90 ? 1 : angle == 90 ? 2 : angle &gt; 90 ? 3 : 0;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring boot 어노테이션]]></title>
            <link>https://velog.io/@jjya_3562/spring-boot-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@jjya_3562/spring-boot-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Wed, 20 Mar 2024 08:13:29 GMT</pubDate>
            <description><![CDATA[<h3 id="noargsconstructor">@NoArgsConstructor</h3>
<p>: 파라미터가 없는 디폴트 생성자를 자동으로 생성
: 기본 생성자 주입</p>
<pre><code class="language-java">@NoArgsConstructor
public class User {
    private Long id;
}</code></pre>
<p>@NoArgsConstructor 어노테이션을 사용하면 해당 코드와 동일하게됨</p>
<pre><code class="language-java">public class User {
    private Long id;

    public User(){}
}</code></pre>
<h4 id="noargsconstructoraccess--accesslevelprotected">@NoArgsConstructor(access = AccessLevel.PROTECTED)</h4>
<p> “아무런 매개변수가 없는 생성자를 생성하되 다른 패키지에 소속된 클래스는 접근을 불허한다” 라는뜻</p>
<h3 id="requiredargsconstructor">@RequiredArgsConstructor</h3>
<p> : final 이나 @NonNull 인 필드값만 파라미터로 받는 생성자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 - 코딩테스트] 중앙값 구하기]]></title>
            <link>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%91%EC%95%99%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0-fworpfr8</link>
            <guid>https://velog.io/@jjya_3562/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%91%EC%95%99%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0-fworpfr8</guid>
            <pubDate>Mon, 18 Mar 2024 07:26:48 GMT</pubDate>
            <description><![CDATA[<h3 id="java-0단계-중앙값-구하기">JAVA 0단계 중앙값 구하기</h3>
<p><img src="https://velog.velcdn.com/images/jjya_3562/post/1cf9c5c1-4e61-45a1-8aea-ca5297a68fe1/image.png" alt=""></p>
<h3 id="나의-문제풀이">나의 문제풀이</h3>
<pre><code class="language-java">import java.util.Arrays;

class Solution {
    public int solution(int[] array) {
        Arrays.sort(array);
        int answer = array.length / 2;
        return array[answer];
    }
}</code></pre>
<p>오름차순 정렬에 힌트를 얻어 문제를 해결하였다</p>
<h3 id="정렬">정렬</h3>
<pre><code class="language-java">// 기본 공통
import java.util.Arrays;

// 공통 배열
int array[] = {4,11,67,2,0}</code></pre>
<h4 id="1-배열-오름차순-정렬">1. 배열 오름차순 정렬</h4>
<pre><code class="language-java">Arrays.sort(array);</code></pre>
<p>=&gt; {0,2,4,11,67}</p>
<h4 id="2-배열-내림차순-정렬">2. 배열 내림차순 정렬</h4>
<pre><code class="language-java">Arrays.sort(array,Collections.reverseOrder());</code></pre>
<p>=&gt; {67,11,4,2,0}</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring boot 로그인, 회원가입(3)]]></title>
            <link>https://velog.io/@jjya_3562/Spring-boot-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%853</link>
            <guid>https://velog.io/@jjya_3562/Spring-boot-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%853</guid>
            <pubDate>Thu, 21 Dec 2023 06:43:10 GMT</pubDate>
            <description><![CDATA[<h2 id="회원가입">회원가입</h2>
<h3 id="1-controller-작성">1. Controller 작성</h3>
<pre><code class="language-java">@RequiredArgsConstructor
@RestController
public class UserController {
    private final UserService userservice;

    @PostMapping(&quot;/register&quot;)
    public ResponseEntity&lt;?&gt; join(@RequestBody @Valid UserRequest.JoinDTO request, Error error) {
        userservice.join(request);
        return ResponseEntity.ok( ApiUtils.success(null) );
    }


    @PostMapping(&quot;/check&quot;)
    public ResponseEntity&lt;?&gt; check(@RequestBody @Valid UserRequest.JoinDTO requestDTO, Error error) {
        userservice.checkEmail(requestDTO.getEmail());
        return ResponseEntity.ok( ApiUtils.success(null) );
    }</code></pre>
<p>회원가입 진행시 이미 가입된 아이디가 있는지 확인을 위해 check 메소드도 함께 생성함</p>
<h3 id="2-service-작성">2. service 작성</h3>
<pre><code class="language-java">@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final AuthenticationManager authenticationManager;

    public void checkEmail(String email) {
        // 동일한 이메일이 있는지 확인.
        Optional&lt;User&gt; users = userRepository.findByEmail(email);
        if(users.isPresent()) {
            throw new Exception400(&quot;이미 존재하는 이메일 입니다. : &quot; + email);
        }
    }

    @Transactional
    public void join(UserRequest.JoinDTO requestDTO) {
        checkEmail(requestDTO.getEmail());
        String encodedPassword = passwordEncoder.encode( requestDTO.getPassword());
        requestDTO.setPassword(encodedPassword);
        try {
            userRepository.save(requestDTO.toEntity());
        }catch (Exception e){
            throw new Exception500(e.getMessage());
        }
    } 
} </code></pre>
<p>비밀번호 인코딩후 저장하기
<br><br> </p>
<p>페이지 생성전 포스트맨으로 먼저 확인해보자!
회원가입 성공
<img src="https://velog.velcdn.com/images/jjya_3562/post/7a24826a-8c50-4a28-ae57-9d08d9f93abc/image.png" alt=""><img src="https://velog.velcdn.com/images/jjya_3562/post/bdf91dfa-2ea0-4896-b4dc-eb25ca5d2fcd/image.png" alt=""></p>
<p>가입한 아이디가 존재 할 경우<img src="https://velog.velcdn.com/images/jjya_3562/post/b277fab2-d0a5-4fdd-93be-19c6c593069e/image.png" alt=""></p>
<h3 id="3-joinhtml">3. join.html</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Join Form&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;div class=&quot;memberWrap&quot;&gt;
    &lt;div class=&quot;inner&quot;&gt;
        &lt;h1&gt;Join&lt;/h1&gt;

        &lt;form id=&quot;joinForm&quot;&gt;
            &lt;label for=&quot;email&quot;&gt;Email:&lt;/label&gt;
            &lt;input type=&quot;email&quot; id=&quot;email&quot; name=&quot;email&quot; required&gt;&lt;br&gt;&lt;br&gt;

            &lt;label for=&quot;password&quot;&gt;Password:&lt;/label&gt;
            &lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot; required&gt;&lt;br&gt;&lt;br&gt;

            &lt;label for=&quot;username&quot;&gt;Username:&lt;/label&gt;
            &lt;input type=&quot;text&quot; id=&quot;username&quot; name=&quot;username&quot; required&gt;&lt;br&gt;&lt;br&gt;
            &lt;label for=&quot;phoneNumber&quot;&gt;PhoneNumber:&lt;/label&gt;
            &lt;input type=&quot;text&quot; id=&quot;phoneNumber&quot; name=&quot;phoneNumber&quot; required&gt;&lt;br&gt;&lt;br&gt;

            &lt;input type=&quot;submit&quot; value=&quot;Submit&quot;&gt;


        &lt;/form&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;

    document.addEventListener(&#39;DOMContentLoaded&#39;, function() {
        const form = document.getElementById(&#39;joinForm&#39;);
        form.addEventListener(&#39;submit&#39;, async function(e) {
            e.preventDefault();

            const email = document.getElementById(&#39;email&#39;).value;
            const password = document.getElementById(&#39;password&#39;).value;
            const username = document.getElementById(&#39;username&#39;).value;
            const phoneNumber = document.getElementById(&#39;phoneNumber&#39;).value;

            const data = {
                email,
                password,
                username,
                phoneNumber
            };

            try {
                const response = await fetch(&#39;http://localhost:8080/register&#39;, {
                    method: &#39;POST&#39;,
                    headers: {
                        &#39;Content-Type&#39;: &#39;application/json&#39;
                    },
                    body: JSON.stringify(data)
                });

                const result = await response.json();

                if (result.success()) {
                    alert(&quot;회원가입에 성공했습니다!&quot;);
                    window.location.href = &#39;/&#39;;
                } else {
                    alert(&quot;Error: &quot; + result.error.message);
                }

            } catch (error) {
                console.error(&#39;Error:&#39;, error);
            }
        });
    });


    function getQueryVariable(variable) {
        var query = window.location.search.substring(1);
        var vars = query.split(&quot;&amp;&quot;);
        for (var i = 0; i &lt; vars.length; i++) {
            var pair = vars[i].split(&quot;=&quot;);
            if (decodeURIComponent(pair[0]) == variable) {
                return decodeURIComponent(pair[1]);
            }
        }
        return null;
    }

    // 메시지 표시
    var message = getQueryVariable(&quot;msg&quot;);
    if (message !== null) {
        alert(message);
    }
&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><br><br></p>
<h2 id="로그인">로그인</h2>
<p>jwt를 발급후 header에 실어 client에 보내는 작업을 진행할 예정</p>
<h3 id="1-controller">1. Controller</h3>
<pre><code class="language-java">@PostMapping(value = &quot;/login&quot;)                                                                                               
public ResponseEntity&lt;?&gt; signin(@RequestBody UserRequest.JoinDTO request, HttpServletResponse response, Error error) {       
  String jwt = userservice.login(request); 
  // &quot;Bearer &quot; 접두사 제거     
  jwt = jwt.replace(JwtTokenProvider.TOKEN_PREFIX, &quot;&quot;);  

  // 쿠키 설정         
  Cookie cookie = new Cookie(&quot;jwtToken&quot;, jwt);    
  cookie.setHttpOnly(true);   
  cookie.setPath(&quot;/&quot;); // 모든 경로에서 쿠키 접근 가능      
  response.addCookie(cookie);                                          

  return ResponseEntity.ok().header(JwtTokenProvider.HEADER, jwt)                                                          
            .body(ApiUtils.success(null));
}                    </code></pre>
<p>jwt토큰을 쿠키에 저장해서 클라이언트에게 넘겨준다!
쿠키에 저장하는건 클라이언트쪽에서 맞는거 같다
이부분은 추후 수정이 필요해보인다</p>
<h3 id="2-service">2. Service</h3>
<pre><code class="language-java">    @Transactional
    public String login(UserRequest.JoinDTO requestDTO) {
        // ** 인증 작업.
        try{
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                    = new UsernamePasswordAuthenticationToken(requestDTO.getEmail(), requestDTO.getPassword());
            //사용자의 이메일과 패스워드를 포함한 인증 토큰을 생성합
            Authentication authentication =  authenticationManager.authenticate(
                    usernamePasswordAuthenticationToken
            );
            // CustomUserDetailsService에 loadUserByUsername가 실행됨
            // authentication여기에 내 로그인한 정보가 담김

            //사용자가 제공한 이메일과 비밀번호로 사용자를 인증하려고 하는것
            // ** 인증 완료 값을 받아온다.
            CustomUserDetails customUserDetails = (CustomUserDetails)authentication.getPrincipal();

            // ** 토큰 발급 - 이 JWT 토큰은 사용자 인증을 통해 확인된 사용자의 정보를 포함
            return JwtTokenProvider.create(customUserDetails.getUser());
        }catch (Exception e){
            // 401 반환.
            throw new Exception401(&quot;인증되지 않음.&quot;);
        }
    }</code></pre>
<p>사용자를 인증하고 인증된 사용자 정보를 담은 jwt토큰을 전달해준다.</p>
<h3 id="3-loginhtml">3. login.html</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

&lt;!--&lt;link rel=&quot;stylesheet&quot; th:href=&quot;@{/css/member.css}&quot;&gt;--&gt;
&lt;script src=&quot;https://code.jquery.com/jquery-3.6.0.min.js&quot;&gt;&lt;/script&gt;


&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Login&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class=&quot;memberWrap&quot;&gt;
    &lt;div class=&quot;inner&quot;&gt;
        &lt;h1&gt;Login&lt;/h1&gt;
        &lt;form id=&quot;loginForm&quot;&gt;
            &lt;label for=&quot;email&quot;&gt;Email:&lt;/label&gt;
            &lt;input type=&quot;email&quot; id=&quot;email&quot; name=&quot;email&quot; required&gt;&lt;br&gt;&lt;br&gt;
            &lt;br&gt;
            &lt;label for=&quot;password&quot;&gt;Password:&lt;/label&gt;
            &lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot; required&gt;
            &lt;br&gt;
            &lt;input type=&quot;submit&quot; value=&quot;login&quot;&gt;
        &lt;/form&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
    document.addEventListener(&#39;DOMContentLoaded&#39;, function () {
        const form = document.getElementById(&#39;loginForm&#39;);
        form.addEventListener(&#39;submit&#39;, async function (e) {
            e.preventDefault();

            const email = document.getElementById(&#39;email&#39;).value;
            const password = document.getElementById(&#39;password&#39;).value;

            const data = {
                email,
                password
            };

            try {
                const response = await fetch(&#39;http://localhost:8080/login&#39;, {
                    method: &#39;POST&#39;,
                    headers: {
                        &#39;Content-Type&#39;: &#39;application/json&#39;
                    },
                    body: JSON.stringify(data)
                });

                const result = await response.json();

                if (response.ok) {

                    alert(&quot;로그인에 성공했습니다!&quot;);
                    window.location.href = &#39;/&#39;;
                } else {
                    alert(&quot;Error: &quot; + result.error.message);
                }

            } catch (error) {
                console.error(&#39;Error:&#39;, error);
            }
        });
    });
&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>로그인 완료! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 시큐리티] SecurityConfig]]></title>
            <link>https://velog.io/@jjya_3562/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-SecurityConfig</link>
            <guid>https://velog.io/@jjya_3562/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-SecurityConfig</guid>
            <pubDate>Thu, 21 Dec 2023 04:04:07 GMT</pubDate>
            <description><![CDATA[<p>Spring Security 환경 설정을 구성하기 위한 클래스이다.</p>
<pre><code class="language-java">
/**
 * Spring Security 환경 설정을 구성하기 위한 클래스
 * 웹 서비스가 로드 될때 Spring Container 의해 관리가 되는 클래스
 * 사용자에 대한 &#39;인증&#39;, &#39;인가&#39;에 대한 구성을 Bean 메서드로 주입함
 *
 */


@Slf4j
@RequiredArgsConstructor
@Configuration // ** 현재 클래스를 (설정 클래스)로 설정
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {

        return authenticationConfiguration.getAuthenticationManager();
        /* BCrypt : 기본으로 사용. 가장 많이 사용되는 알고리즘.
         * SCrypt : 개발자가 직접 필요에 따라 변경 가능.
         * Argon2
         * PBKDF2
         * MD5
         * SHA-1, SHA-256 등
         */
    }

    public class CustomSecurityFilterManager extends AbstractHttpConfigurer&lt;CustomSecurityFilterManager, HttpSecurity&gt;{

        @Override
        public void configure(HttpSecurity httpSecurity) throws Exception {

            AuthenticationManager authenticationManager = httpSecurity.getSharedObject(
                    AuthenticationManager.class
            );

            httpSecurity.addFilter(new JwtAuthenticationFilter(authenticationManager));

            super.configure(httpSecurity);
        }
    }

    /*
     * HTTP에 대해서 &#39;인증&#39;과 &#39;인가&#39;를 담당하는 메서드
     * 필터를 통해 인증 방식과 인증 절차에 대해서 등록하며 설정을 담당하는 메서드
     * */

    @Bean // 스프링 빈으로 등록
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 1. CSRF 해제 - 서버에 인증정보를 저장하지 않기때문에
        http.csrf().disable(); // postman 접근해야 함!! - CSR 할때!!

        // 2. iframe 거부 설정
        http.headers().frameOptions().sameOrigin();

        // 3. cors 재설정
        http.cors().configurationSource(configurationSource());

        // 4. jSessionId 사용 거부
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 세션 정책

        // 5. form 로긴 해제 (UsernamePasswordAuthenticationFilter 비활성화) (폼 로그인 비활성화)
        http.formLogin().disable();

        // 6. 로그인 인증창이 뜨지 않게 비활성화(기본 인증 비활성화)
        http.httpBasic().disable(); //bearer 방식으로

        // 7. 커스텀 필터 적용 (시큐리티 필터 교환) 커스텀 필터 적용
        http.apply(new CustomSecurityFilterManager());

        // 8. 인증 실패 처리
        http.exceptionHandling().authenticationEntryPoint((request, response, authException) -&gt; {
            log.warn(&quot;인증되지 않은 사용자가 자원에 접근하려 합니다 : &quot; + authException.getMessage());
            FilterResponseUtils.unAuthorized(response, new Exception401(&quot;인증되지 않았습니다&quot;));
        });

        // 9. 권한 실패 처리
        http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -&gt; {
            log.warn(&quot;권한이 없는 사용자가 자원에 접근하려 합니다 : &quot; + accessDeniedException.getMessage());
            FilterResponseUtils.forbidden(response, new Exception403(&quot;권한이 없습니다&quot;));
        });

        // 10. 인증, 권한 필터 설정 - 경로에 대한 인증 설정
        http.authorizeRequests(
                authorize -&gt; authorize
                        .antMatchers(&quot;/carts/**&quot;, &quot;/options/**&quot;, &quot;/orders/**&quot;)
                        .authenticated()

                        .antMatchers(&quot;/admin/**&quot;)
                        .access(&quot;hasRole(&#39;ADMIN&#39;)&quot;)
                        // (&quot;/admin/**&quot;)에 대한 요청은 ADMIN권한을 가진 회원만 승인한다.
                        // 회원 권한을 설정할때 , 반드시 &quot;ROLE_&quot;을 붙여야만 Security가 인식함
                        .anyRequest().permitAll() //다른 주소는 모두 허용
                // 모든 요청에 대해 인증을 요구 하지 않는다
                // 위에서 설정한 특정 경로에 대한 권한 인증 확인만 하고 다른 경로는 확인하지 않는다
        );

        // 12. 로그아웃 관련 설정 (이 부분 추가)
        http.logout()
                .logoutUrl(&quot;/logout&quot;)
                .logoutSuccessHandler((request, response, authentication) -&gt; {
                    response.sendRedirect(&quot;/&quot;);
                })
                .deleteCookies(&quot;jwtToken&quot;);

        return http.build();
    }

    // ** 규칙: 헤더(Authorization), 메서드, IP 주소, 클라이언트으 쿠키 요청을 허용
    public CorsConfigurationSource configurationSource() {
        CorsConfiguration corsConfigurationSource = new CorsConfiguration();
        corsConfigurationSource.addAllowedHeader(&quot;*&quot;); // 모든 헤더를 허용
        corsConfigurationSource.addAllowedMethod(&quot;*&quot;); // GET, POST, PUT, DELETE 등의 모든 메서드를 허용
        corsConfigurationSource.addAllowedOriginPattern(&quot;*&quot;); // 모든 IP주소를 허용
        corsConfigurationSource.setAllowCredentials(true); // 클라이언트 쿠키 요청 허용
        corsConfigurationSource.addExposedHeader(&quot;Authorization&quot;); // 헤더

        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource
                = new UrlBasedCorsConfigurationSource();

        // ** (/) 들어오는 모든 유형의 URL 패턴을 허용.
        urlBasedCorsConfigurationSource.registerCorsConfiguration(&quot;/**&quot;, corsConfigurationSource);
        return urlBasedCorsConfigurationSource;
    }
}</code></pre>
<ol>
<li><p>@Configuration: 이 클래스가 구성 클래스임을 나타내는 어노테이션</p>
</li>
<li><p>passwordEncoder() : 패스워드 인코더를 빈으로 등록</p>
<pre><code class="language-java">@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
   return authenticationConfiguration.getAuthenticationManager();
}</code></pre>
</li>
<li><p>인증 매니저 빈으로 등록
: 실질적으로는 AuthenticationManager에 등록된 AuthenticationProvider에 의해 인증이 처리됨</p>
</li>
</ol>
<ol start="4">
<li>CustomSecurityFilterManager
: AbstractHttpConfigurer를 확장하여 커스텀 보안 필터 매니저를 정의 함</li>
</ol>
<ol start="5">
<li><p>SecurityFilterChain 빈
: SecurityFilterChain을 사용하여 전반적인 보안 설정을 구성함
: HttpSecurity 객체를 사용하여 다양한 구성이 적용</p>
<ul>
<li><p>http.csrf().disable();
: CSRF 비활성화</p>
</li>
<li><p>http.headers().frameOptions().sameOrigin(); 
: iframe 거부 설정</p>
</li>
<li><p>http.cors().configurationSource(configurationSource());
: cors 설정</p>
</li>
</ul>
</li>
</ol>
<ul>
<li><p>http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
: 세션을 사용하지 않음</p>
</li>
<li><p>http.formLogin().disable();
: 폼 로그인 비활성화</p>
</li>
<li><p>http.httpBasic().disable();
: 기본 HTTP 인증 비활성화</p>
</li>
<li><p>http.apply(new CustomSecurityFilterManager());
: 커스텀 필터 적용</p>
</li>
</ul>
<ul>
<li><p>인증 및 권한 설정</p>
<pre><code class="language-java">  http.authorizeRequests(
          authorize -&gt; authorize
                  .antMatchers(&quot;/petsitter/**&quot;,&quot;/carts/**&quot;, &quot;/options/**&quot;, &quot;/orders/**&quot;).authenticated()
                  .antMatchers(&quot;/admin/**&quot;).access(&quot;hasRole(&#39;ADMIN&#39;)&quot;)
                  .anyRequest().permitAll()
  );
</code></pre>
<p>.antMatchers(&quot;&quot;).authenticated(): 해당 경로는 인증이 필요함
  .antMatchers(&quot;/admin/**&quot;).access(&quot;hasRole(&#39;ADMIN&#39;)&quot;) : 해당 경로는 ADMIN권한을 가진 사용자만 접근 가능
  그외 모든 사용자에게 허용</p>
</li>
<li><p>인증 및 권한 실패 처리</p>
<pre><code class="language-java">http.exceptionHandling().authenticationEntryPoint((request, response, authException) -&gt; {
    // 인증 실패 처리
});

http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -&gt; {
    // 권한 실패 처리
});</code></pre>
<p>: 인증 실패 및 권한 실패 시의 처리를 정의
: 인증 실패 시 401응답을 보내고, 권한 실패시 403응답을 보냄</p>
</li>
<li><p>로그아웃 관련</p>
<pre><code class="language-java">http.logout()
      .logoutUrl(&quot;/logout&quot;)
      .logoutSuccessHandler((request, response, authentication) -&gt; {
          // 로그아웃 성공 시 처리
      })
      .deleteCookies(&quot;jwtToken&quot;);</code></pre>
<p>: 로그아웃 URL을 &quot;/logout&quot;으로 지정하고, 로그아웃 성공 시 처리 및 쿠키 삭제를 설정</p>
</li>
</ul>
<ol start="6">
<li><p>CORS 구성 메서드</p>
<pre><code class="language-java">public CorsConfigurationSource configurationSource() {
     CorsConfiguration corsConfigurationSource = new CorsConfiguration();
     corsConfigurationSource.addAllowedHeader(&quot;*&quot;); // 모든 헤더를 허용
     corsConfigurationSource.addAllowedMethod(&quot;*&quot;); // GET, POST, PUT, DELETE 등의 모든 메서드를 허용
     corsConfigurationSource.addAllowedOriginPattern(&quot;*&quot;); // 모든 IP주소를 허용
     corsConfigurationSource.setAllowCredentials(true); // 클라이언트 쿠키 요청 허용
     corsConfigurationSource.addExposedHeader(&quot;Authorization&quot;); // 헤더

     UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource
             = new UrlBasedCorsConfigurationSource();

     // ** (/) 들어오는 모든 유형의 URL 패턴을 허용.
     urlBasedCorsConfigurationSource.registerCorsConfiguration(&quot;/**&quot;, corsConfigurationSource);
     return urlBasedCorsConfigurationSource;
 }</code></pre>
<p>: CORS(Cross-Origin Resource Sharing) 설정을 정의함
: 모든 헤더, 메서드, IP 주소, 클라이언트 쿠키 요청을 허용하도록 설정</p>
</li>
</ol>
<blockquote>
<p>CORS(Cross-Origin Resource Sharing)
웹 페이지에서 실행중인 스크립트가 다른 도메인에 있는 리소스에 접근하는 권한을 부여하는 보안기술
동일 출처 정책(same-Origin Policy)에 따라 다른 도메인에서 온 요청에 대한 보안을 강화 함</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인/회원가입 기능 구현]]></title>
            <link>https://velog.io/@jjya_3562/1.-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jjya_3562/1.-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Thu, 21 Dec 2023 02:21:48 GMT</pubDate>
            <description><![CDATA[<p>spring security를 이용해 로그인/회원가입 을 구현작업 진행</p>
<p>해당 부분 작업을 진행하면서 많은 고민에 빠졌다
jwt는 무엇이고 seccion과 cookei는 무엇이며, security는 어떻게 동작하는것인지
많이 찾아봤지만 역시나 공부가 더 필요해 보인다.</p>
<p>일단 이번 프로젝트에서는 
: 로그인 작업 진행시 Jwt토큰을 생성해서 header에 넣어 Client에게 전달할 예정</p>
<ol>
<li>사용자가 로그인을 합니다</li>
<li>DB에서 사용자를 확인합니다</li>
<li>jwt토큰 발급받아서 cookie에 저장합니다</li>
<li>데이터 요청에 cookie에 담긴 토큰과 함께 요청합니다</li>
<li>jwt토큰을 검증합니다</li>
<li>검증이 완료 되면 데이터를 응답합니다</li>
<li>jwt토큰은 일정 시간이 지나면 만료 됩니다</li>
<li>Cookie가 있는지 jwt토큰이 만료되었는지 확인하고 에러를 보냅니다.</li>
<li>응답으로 에러를 받고 로그인 페이지로 이동시킵니다.</li>
<li>재로그인을 해야 합니다.</li>
</ol>
<h3 id="보안사항-고려">보안사항 고려</h3>
<ol>
<li><p>XSS(Cross-Site Scripting)
XSS는 대상 웹사이트에 악성 스크립트를 주입하여 비정상적인 동작을 실행시키는 공격 유형이다. 공격자는 다른 사용자의 브라우저에서 스크립트가 실행되게 하여 사용자의 쿠키, 세션 등 중요한 정보에 접근할 수 있다.</p>
<p>해결 방법 : HttpOnly 속성 사용</p>
<p>HttpOnly 속성은 웹브라우저가 스크립트를 통한 Dom document.cookie 객체 접근을 허용하지 않도록 합니다. 즉 공격자의 XSS를 통한 토큰 탈취를 방지할 수 있습니다.</p>
<pre><code>cookie.setHttpOnly(true);</code></pre></li>
<li><p>CSRF ( Cross Site Request Forgery)
사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하도록 만드는 공격 유형이다. 공격자는 사용자의 엑세스 토큰을 탈취하지 않고도 사용자로 하여금 엑세스 토큰을 사용하여</p>
<p>해결 방법 : SameSite 속성 사용</p>
<p>SameSite 속성은 웹브라우저가 쿠키를 받은 서버가 아닌 다른 서버로 요청을 하는 경우 그 쿠키는 전송하지 못하도록 합니다.</p>
</li>
</ol>
<p>쿠키로 저장하는 방법을 선택 했으니 토큰의 도난을 사전방지를 생각해야한다!
추후 Refresh Token을 생성해서 radis에 저장하는 방식으로 기능 고도화를 해보려한다.</p>
<h3 id="spring-security-로그인-회원가입-구현">spring security 로그인 회원가입 구현</h3>
<p><a href="https://velog.io/@jjya_3562/series/Spring-security-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85">로그인 / 회원가입 구현 확인하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[erd작성하기]]></title>
            <link>https://velog.io/@jjya_3562/erd%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jjya_3562/erd%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 21 Dec 2023 01:57:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jjya_3562/post/031259a8-7d17-47b6-99fe-f6c10ec3def3/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[요구사항 명세서]]></title>
            <link>https://velog.io/@jjya_3562/%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%AA%85%EC%84%B8%EC%84%9C</link>
            <guid>https://velog.io/@jjya_3562/%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%AA%85%EC%84%B8%EC%84%9C</guid>
            <pubDate>Mon, 18 Dec 2023 14:02:24 GMT</pubDate>
            <description><![CDATA[<h2 id="🚀프로젝트-명">🚀프로젝트 명</h2>
<p>반려동물 돌봄 서비스 (펫시터 예약 및 결제 시스템)</p>
<h2 id="🚀요구사항">🚀요구사항</h2>
<h3 id="1-회원가입-로그인">1. 회원가입/ 로그인</h3>
<p>Spring Sequrity를 이용해 회원 가입 로그인을 진행.
로그인시 session에 사용자 정보 저장
요청자가 누구인지 확인할 방법 생각해보기 (기능고도화)</p>
<ol>
<li>쿠키에 저장후 사용</li>
<li>리프레시 토큰 발급 후 Redis에 저장 해서 사용</li>
</ol>
<ul>
<li><p><strong>회원가입</strong> </p>
<ul>
<li>회원번호(pk), 아이디(이메일 - unique), 비밀번호, 회원이름, 전화번호 </li>
<li>권한, 주소, 프로필</li>
</ul>
</li>
<li><p><strong>로그인</strong> </p>
<ul>
<li>아이디(notnull), 비밀번호(notnull) 로그인</li>
</ul>
</li>
</ul>
<br> 

<h3 id="2-반려동물-정보-입력">2. 반려동물 정보 입력</h3>
<ul>
<li><p><strong>등록</strong> </p>
<ul>
<li>반려동물 정보등록 페이지 작성(Creat) </li>
<li>반려동물 회원 @ManyToOne  </li>
<li>타입(강아지/고양이), 이름, 성별, 품종, 생일, 몸무게, 중성화 수술여부</li>
</ul>
</li>
<li><p><strong>수정</strong></p>
<ul>
<li>반려동물 pk로 조회해서 반려동물 수정작업(update)</li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li>해당 아이디의 반려동물 정보 삭제 (delete)</li>
<li>조회에서 삭제 가능</li>
</ul>
</li>
<li><p><strong>조회</strong> </p>
<ul>
<li>해당 아이디가 가지고 있는 반려동물 정보 조회 paging</li>
<li>상세페이지 없이 리스트에서만 확인 가능</li>
</ul>
</li>
</ul>
<br>

<h3 id="3-펫시터-상품">3. 펫시터 (상품)</h3>
<ul>
<li><p><strong>등록</strong> </p>
<ul>
<li>펫시터 정보등록 페이지 작성(Creat) </li>
<li>로그인후 작성 가능</li>
<li>펫시터 회원 @ManyToOne  </li>
<li>제목, 내용, 지역, 가격, 사진, 사용자(로그인)</li>
</ul>
</li>
<li><p><strong>수정</strong></p>
<ul>
<li>해당 아이디가 가지고 있는 펫시터 정보 수정</li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li>펫시터 pk로 펫시터 정보 삭제</li>
</ul>
</li>
<li><p><strong>전체조회</strong> </p>
<ul>
<li>해당 아이디가 가지고 있는 펫시터 정보 수정</li>
<li>추후 기능 고도화 : 지역 셀렉시 해당 지역에 해당하는 게시글 보여주기</li>
</ul>
</li>
<li><p><strong>상세페이지조회</strong></p>
<ul>
<li>제목, 내용, 지역, 작성일, 가격, 지역, 사진</li>
<li>찜 등록 버튼, 결제 하기 버튼 클릭시 주문 페이지로 넘어가기<br>

</li>
</ul>
</li>
</ul>
<h3 id="4-주문">4. 주문</h3>
<ul>
<li><p><strong>등록</strong> </p>
<ul>
<li>회원정보 @OneToMany  </li>
<li>펫시터 정보 @OntoOne  </li>
<li>일정 정보 매핑 @OneToOne - 날짜,시간정보</li>
<li>반려동물 정보 @OneToMany </li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li>주문 상품 삭제</li>
</ul>
</li>
<li><p><strong>전체조회</strong> </p>
<ul>
<li>주문 상품 리스트 조회 </li>
</ul>
</li>
</ul>
<br>

<h3 id="5-찜">5. 찜</h3>
<ul>
<li><p><strong>등록</strong> </p>
<ul>
<li>펫시터 상세페이지에 하트 클릭시 찜 리스트로 등록</li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li>찜 리스트에서 삭제 버튼으로 삭제 가능</li>
</ul>
</li>
<li><p><strong>전체조회</strong> </p>
<ul>
<li>찜 리스트 작성, paging 작업</li>
</ul>
</li>
</ul>
<br>

<h3 id="6-게시판">6. 게시판</h3>
<ul>
<li><p><strong>등록</strong> </p>
<ul>
<li>회원 정보 @ManyToOne</li>
<li>제목, 작성일, 내용, 파일</li>
<li>파일 @OneToMany</li>
<li>파일이름, 파일경로, 파일사이즈, 파일타입, uuid</li>
</ul>
</li>
<li><p><strong>수정</strong></p>
<ul>
<li>게시판 수정</li>
</ul>
</li>
<li><p><strong>삭제</strong>    </p>
<ul>
<li>게시판 삭제</li>
</ul>
</li>
<li><p><strong>전체조회</strong> </p>
<ul>
<li>게시판 리스트 조회 paging 설정</li>
</ul>
</li>
<li><p><strong>상세페이지</strong> </p>
<ul>
<li>제목, 내용, 작성자, 작성시간</li>
<li>업로드 파일 - 다운로드 </li>
<li>게시판 - 댓글 @oneToMany  </li>
<li>댓글 아이디, 댓글 작성일, 댓글 작성자, 댓글 내용</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jjya_3562/post/edb53730-07dd-40b4-9e69-d8de8b2e781a/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>