text:
<file_sep>import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Programmers_길찾기게임 {
	public static void main(String[] args) {
		int res[][] = solution(new int[][]{{5,3},{11,5},{13,3},{3,5},{6,1},{1,3},{8,6},{7,2},{2,2}});
		System.out.println(Arrays.toString(res[0]));
		System.out.println(Arrays.toString(res[1]));
	}

	static int idx;
    public static int[][] solution(int[][] nodeinfo) {

    	List<Data> list = new ArrayList<Data>();
    	for(int i=0; i<nodeinfo.length; i++) {
    		list.add(new Data(nodeinfo[i][0], nodeinfo[i][1], i+1));
    	}

        Node root = Sort(0, nodeinfo.length-1, list);
        int res[][] = new int[2][nodeinfo.length];

        idx = 0;
        pre(root, res);
        idx = 0;
        post(root, res);

        return res;
    }

    public static void pre(Node root, int res[][]) {
        if(root == null) {
            return;
        }

        res[0][idx++] = root.index;
        pre(root.left, res);
        pre(root.right, res);
    }

    public static void post(Node root, int res[][]) {
        if(root == null) {
            return;
        }

        post(root.left, res);
        post(root.right, res);
        res[1][idx++] = root.index;
    }

    public static Node Sort(int start, int end, List<Data> list) {
        if(start > end) {
            return null;
        }

        int pivot = partition(start, end, list);
        Node root = new Node(list.get(pivot).index, null, null);
        root.left = Sort(start, pivot-1, list);
        root.right = Sort(pivot+1, end, list);
        return root;
    }

    public static int partition(int start, int end, List<Data> list) {
        int maxVal = -1;
        for(int i=start; i<=end; i++) {
        	if(maxVal == -1) {
        		maxVal = i;
        	} else if(list.get(maxVal).y < list.get(i).y) {
                maxVal = i;
            }
        }

        int temp1 = list.get(maxVal).x;
        int temp2 = list.get(maxVal).y;
        int temp3 = list.get(maxVal).index;
        list.get(maxVal).x = list.get(end).x;
        list.get(maxVal).y = list.get(end).y;
        list.get(maxVal).index = list.get(end).index;
        list.get(end).x = temp1;
        list.get(end).y = temp2;
        list.get(end).index = temp3;

        int pivot = start-1;
        for(int i=start; i<end; i++) {
            if(list.get(end).x > list.get(i).x) {
                temp1 = list.get(++pivot).x;
                temp2 = list.get(pivot).y;
                temp3 = list.get(pivot).index;
                list.get(pivot).x = list.get(i).x;
                list.get(pivot).y = list.get(i).y;
                list.get(pivot).index = list.get(i).index;
                list.get(i).x = temp1;
                list.get(i).y = temp2;
                list.get(i).index = temp3;
            }
        }

        temp1 = list.get(++pivot).x;
        temp2 = list.get(pivot).y;
        temp3 = list.get(pivot).index;
        list.get(pivot).x = list.get(end).x;
        list.get(pivot).y = list.get(end).y;
        list.get(pivot).index = list.get(end).index;
        list.get(end).x = temp1;
        list.get(end).y = temp2;
        list.get(end).index = temp3;

        return pivot;
    }

    private static class Node {
    	int index;
        Node left, right;
        public Node(int index, Node left, Node right) {
        	this.index = index;
            this.left = left;
            this.right = right;
        }
    }

    private static class Data {
    	int x, y, index;
    	public Data(int x, int y, int index) {
    		this.x = x;
    		this.y = y;
    		this.index = index;
    	}
    }
}
<file_sep># Java

## JVM (Java Virtual Machine)
### JVM이란 ?
* JVM이란 Java Byte Code를 OS에 맞게 해석하여 운영체제에 독립적인 플랫폼을 갖게 해줌

* 프로그램의 메모리 관리를 스스로 하여 C프로그램 처렁 직접 메모리할당을 하지 않음

## 가비지컬렉션 (Garbage Collection)
### 가비지컬렉션이란 ?
* JVM의 Heap영역에서 이루어지는 것으로 이미 할당된 메모리에서 더 이상 사용하지 않는 메모리를 해제하는 행동 (자동으로 실행)
* 사용되지 않는 메모리의 대상은 Heap과 Method Area에서 사용되지 않는 Object를 의미한다.
* 소스상의 close()와 같은 메서드는 Object의 사용중지 의사표현일 뿐 메모리에서 삭제하겠다는 의미가 아님
* 개발자는 System.GC()를 통해 명시적으로 가비지컬렉션을 호출할 수 있지만 메서드 호출은 Full GC를 수행시키는 메서드이기 때문에 Suspend Time(stop-the-world) 시간이 길고 무거운 작업이며 또한 반드시 즉시 수행한다는 보장도 없기 때문에 지양하는 메서드이다.

### 룸셋과 가비지컬렉션
* 가비지컬렉션은 말 그대로 가비지(더 이상 참조(사용)되지 않는 Object)를 모으는 작업
* RoomSet에서 어떤 식으로든 참조 관계가 있다면 Reachable Object, 즉 현재 사용하고 있는 Object로 간주하며 그렇지 않은 경우는 Unreachable Object가 된다
* Unreachable Object에는 참조되지만 더 이상 사용되지 않는 Object인 Memory Leak Object도 존재

### 가비지컬렉션이 수행될 때
* 가비지컬렉션이 수행된 메모리의 해지는 할당된 그 자리에서 이루어지며 그로 인해 Heap 메모리 단편화 문제가 발생 (객체를 할당할 자리가 있지만 자리가 듬성듬성 되있고 연속적이지 않아 객체를 할당하지 못하는 현상)
* JVM에서는 이러한 단편화 문제를 해소하기 위해 compaction같은 알고리즘을 사용
* 하지만 compaction은 비용이 큰 작업이기에 TLAB(Thread Local Allocation Block)을 사용하여 단편화 문제를 해결

### JVM Heap구조
* 구조

* JVM Heap에서는 Young, Old, Perm 세 영역으로 나뉨
* 이 중 Perm(Permanent) 영역은 거의 사용되지 않는 영역으로 클래스와 메서드 정보와 같이 자바 언어 레벨에서는 사용X
* 고려하야 될 자바의 메모리 영역은 4개의 영역(Young-Eden, Young-to Survivor, Young-from Survivor, Old-메모리)으로 나뉨
* 메모리에 객체가 생성되면 Eden영역에 객체가 지정
* Eden영역에 데이터가 어느정도 쌓이면 영역에 있던 객체가 survivor 영역으로 옮겨지거나 삭제됨 (Survivor 영역에는 우선순위가 있지 않지만 두 개중 한 곳은 반드시 비어 있어야 됨)
* Survivor 영역 중 비어있는 곳에 Eden 영역에 있던 객체가 할당
* 할당된 Survivor 영역이 차면 GC가 되면서 Eden영역에 있는 객체와 꽉 찬 Survivor 영역에 있는 객체가 비어있는 Survivor영역으로 이동
* 더 큰 객체가 생성되거나 더 이상 Young 영역에 공간이 남지 않으면 객체들은 Old 영역으로 이동

### 가비지컬렉션의 종류
* 마이너 GC
   * Young 영역에서 발생하는 GC
* 메이저 GC
   * Old 영역이나 Perm 영역에서 발생하는 GC

### 가비지컬렉션 방식
* 시리얼 콜렉터(Serial Collector)
   * 구조

   * Young 영역과 Old 영역이 시리얼(연속적)하게 처리되며 하나의 CPU를 사용
   * 처리를 수행할 때 애플리케이션 수행이 정지됨(Stop-the-world)
   * 살아있는 객체들은 Eden영역에 존재
   * Eden영역이 꽉차면 to Survivor로 살아있는 객체가 이동(이동하기에 너무 큰 객체는 Old영역으로 이동), from Survivor영역에 살아있는 객체도 to Survivor영역으로 이동
   * to Survivor영역이 꽉 찼을 경우, Eden영역이나 from Survivor영역에 남아있는 객체들은 Old영역으로 이동
   * Old영역이나 Perm영역에서 Mark-Sweep-Compact 콜렉션 알고리즘을 통해 안쓰는거 표시해서 삭제하고 한 곳으로 모으는 역할을 수행
      * Mark: Old 영역으로 이동된 객체들 중 살아있는 객체를 식별
      * Sweep: Old 영역의 객체들을 훑는 작업을 수행하여 쓰레기 객체를 식별
      * Compact: 필요없는 객체들을 지우고 살아있는 객체들을 한 곳으로 모음
   * 대기시간이 많아도 크게 문제되지 않는 시스템에서 사용(클라이언트 종류의 장비)
   * 시리얼 콜렉터 명시적 지정 방법
      * -XX:+UseSerialGC
* 병렬 콜렉터(Parallel Collector)
   * 구조

   * 다른 CPU가 대기상태로 남아있는 것을 최소화 하는 것이 목표인 콜렉터
   * 시리얼 콜렉터와는 달리 Young영역에서 콜렉션을 병렬로 처리
   * 많은 CPU를 사용하기 때문에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킬 수 있음
   * Old영역의 GC는 시리얼 콜렉터와 마찬가지로 Mark-Sweep-Compact 콜렉션 알고리즘을 사용
   * 병렬 콜렉터 명시적 지정 방법
      * -XX:+UseParallelGC
* 병렬 컴팩팅 콜렉터(Parallel Compacting Collector)
   * Young영역에서는 병렬 콜렉터와 동일하지만 Old영역 GC에서 새로운 알고리즘을 사용
   * 병렬 콜렉터와 동일하게 이 방식도 여러 CPU를 사용하는 서버에 적합
   * 병렬 컴팩팅 콜렉터 명시적 지정 방법
      * -XX:+UseParallelOldGC
* CMS 콜렉터(Concurrent Mark Sweep Collector)
   * 구조

   * 힙 메모리 영역의 크기가 클 때 적합
   * Young 영역에 대한 GC는 병렬 콜렉터와 동일하지만 Old 영역의 GC는 Mark, Sweep, Remark, Concurrent Sweep 단계를 거침
      * Mark: 매우 짧은 대기시간으로 살아있는 객체를 식별
      * Sweep: 서버수행과 동시에 살아있는 객체에 표시를 해놓음
      * Remark: Concurrent표시 단계에서 표시하는 동안 변경된 객체에 대해 다시 표시
      * Concurrent Sweep: 표시되어 있는 쓰레기를 정리
   * CMS 콜렉터 방식은 2개 이상의 프로세스를 사용하는 서버에 적합(웹서버)
   * CMS 콜렉터 명시적 지정 방법
      * -XX:+UseConcMarkSweepGC

## 객체지향 프로그래밍 (Object Oriented Programming)
### 객체지향 프로그래밍이란 ?
* 절차지향 프로그래밍과 같이 입력을 받아 명시된 순서대로 처리하는 것이 아니라 프로그램을 수많은 '객체'라는 기본 단위로 나누어 객체들간 상호작용을 하는 프로그래밍
### 객체란 ?
* 말 그대로 대상을 나타내는 단어
* 하나의 역할을 수행하는 메서드와 변수의 묶음
* ex) 붕어빵
### 객체지향 프로그래밍의 특징
* OOP is A PIE (Object Oriented Programming is Abstract Polymorphism Inherit Encapsulate)
* 추상화(Abstract)
   * 목적과 관련이 없는 부분을 제거하여 필요한 부분만을 표현하기 위한 개념
   * ex) 붕어빵을 만들려고 한다고 가정한다면 붕어빵 재료인 밀가루반죽과 팥만 필요하다고 명확하게 명시
* 다형성(Polymorphism))
   * 형태가 같은데 서로 다른기능을 하는 개념
   * ex) 똑같은 붕어빵 기계인데 하나는 팥붕어빵을 만드는 기계이고 다른 하나는 슈크림붕어빵을 만드는 기계
   * 이름을 명시적으로 나누지 않고 똑같은 이름으로 다른 기능을 제공하기 위해 사용
   * 다형성의 대표적인 것이 오바리이딩(Overriding)
* 상속성(Inherit)
   * 말 그대로 부모가 사용하는 기능들을 상속 받는 개념
   * 똑같은 코드를 중복하는 것을 방지하기 위해 사용
* 캡슐화(Encapsulate)
   * 사용되는 기능들을 하나의 캡슐에 담아서 사용한다는 개념
   * ex) 붕어빵 안에 팥, 슈크림, 밀가루반죽 등이 같이 들어 있는 것을 의미
   * 캡슐화의 가장 주된 역할은 정보은닉
      * 정보은닉: 겉으로 표현할 것만 표현해주고 그렇지 않은 것들은 정보를 숨기는 방법
      * ex) 붕어빵의 겉은 붕어빵 모형을 이룬 밀가루지만 속안에는 어떤 재료가 들어가 있는지를 알 수 없음
### 객체지향 프로그래밍의 장단점
* 장점
   * 코드의 재활용
      * 프로그램이 독립된 객체들로 이루어져 있기 때문에 재활용이 용이
   * 디버깅이 쉽다
   * 코딩이 간편함
* 단점
   * 속도가 느리다
      * 컴파일러에서 처리해야 될 양이 많고 여러가지 정보에 대한 오버헤드가 있기 때문
   * 설계에 많은 시간이 소비
### 클래스란 ?
* 추상화를 한 매개체
* 변수와 메서드의 조합으로 만들어짐
* ex) 붕어빵 틀<file_sep>## 기본설정
* STS에서 Starter Project 생성 (STS 상단바의 Window -> Preference에서 encoding을 입력 후 Workspace, CSS, HTML, JSP, XML 모두 UTF-8로 지정)
   * Name(=> ex) hotelpage-spring), Group(=> ex) com), Artifact(=> ex) hotelpage), Description(=> ex) hotelpage using spring) 원하는 데로 입력
   * 하단의 Next클릭 후 JDBC API, Spring Boot Devtools, Spring Web, MySQL Driver(MySQL 사용 시), MyBatis FrameWork(MyBatis 사용 시), Lombok(Lombok 사용 시) select후 finish
   * STS 3.9.7버전을 사용할 경우 프로젝트에 빨간불이 들어와 있는데 pom.xml에 properties안에 <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>를 기입 후 프로젝트 오른쪽 클릭 후 maven -> update project 클릭

## Swagger 사용방법
* https://mvnrepository.com 에 접속
* 검색창에 springfox 검색
* Springfox Swagger2를 클릭하여 최신버전 클릭 후 dependency를 pom.xml에 복사 붙여넣기
* Springfox Swagger UI를 클릭하여 최신버전 클릭 후 dependency를 pom.xml에 복사 붙여넣기
* 프로젝트 오른쪽 클릭 후 maven -> update project 클릭
* src/main/java/{Group}.{Artifact}에 있는 SpringApplication에 다음과 같이 기입

 ## Properties 설정 (MySQL 등록)
 * 다음 사진과 같이 기입 (주석은 jsp를 사용할 경우, mybatis.config는 MyBatis를 사용할 경우)

## JWT 적용시키기
* https://mvnrepository.com 에 접속
* 검색창에 jjwt 검색
* JSON Web Token Support For The JVM을 클릭하여 최신버전 클릭 후 dependency를 pom.xml에 복사 붙여넣기

* JwtService 만들기
   ```
   import java.util.Date;
   import java.util.Map;

   import org.springframework.stereotype.Component;

   import com.hotelpage.dto.User;

   import io.jsonwebtoken.Claims;
   import io.jsonwebtoken.Jws;
   import io.jsonwebtoken.Jwts;
   import io.jsonwebtoken.SignatureAlgorithm;

   @Component
   public class JwtService {
      private String secretKey = "hotelpage";
      private Long expireTime = 1000L * 60 * 5;

      // 토큰 생성
      public String createToken(User user) {
         return Jwts.builder()
               .setHeaderParam("typ",  "JWT") // 토큰의 타입
               .setSubject("userToken") // 토큰의 제목
               .setExpiration(new Date(System.currentTimeMillis() + expireTime)) // 토큰의 유효기간
               .claim("user", user) // 토큰에 담고 싶은 정보
               .signWith(SignatureAlgorithm.HS256, secretKey.getBytes()) // secretKey를 사용하여 암호화 알고리즘 적용
               .compact(); // 직렬화 처리 (String으로 변경)
      }

      // 토큰을 사용하여 유저정보 얻기
      public Map<String, Object> getUser(String token) {
         Jws<Claims> claims = null;
         try {
            claims = Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token);
         } catch(Exception e) {
            throw new RuntimeException();
         }

         return claims.getBody();
      }

      // 토큰의 유효성 검증
      // 문제가 존재하면 예외 발생, 그렇지 않은 경우는 문제 없다고 판단
      public void checkValid(String token) {
         Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token);
      }
   }
   ```
* JwtInterceptor 만들기
   ```
   import javax.servlet.http.HttpServletRequest;
   import javax.servlet.http.HttpServletResponse;

   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.stereotype.Component;
   import org.springframework.web.servlet.HandlerInterceptor;

   @Component
   public class JwtInterceptor implements HandlerInterceptor {

      @Autowired
      private JwtService jwtService;

      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
         // option 요청은 바로 통과
         if(request.getMethod().equals("OPTIONS")) {
            return true;
         } else {
            String token = request.getHeader("jwt-auth-token"); // request의 header에 jwt-auth-token이라는 key값에 알맞는 value값을 token으로 저장
            if(token != null && token.length() > 0) {
               jwtService.checkValid(token); // 토큰의 유효성 검증
               return true;
            } else { // 해당 토큰의 유효성이 알맞지 않은 경우
               throw new RuntimeException("인증 토큰이 존재하지 않습니다.");
            }
         }
      }
   }
   ```
* src/main/java/{Group}.{Artifact}에 있는 SpringApplication에 다음과 같이 WebMvcConfigurer를 implements 하기

* 다음과 같이 Autowired와 Override 하여 작성하기

* 만약 addCorsMappings와 addInterceptors의 위치가 바뀔 시 에러가 발생할 수 있음
* Controller에서 만들어 놓은 JwtService를 Autowired 한 뒤 로그인에 해당하는 부분에 client의 localStorage에 토큰을 저장할 수 있게 다음과 같이 response로 토큰을 보내기

* client에서 axios를 통해 받은 결과값의 header에서 다음과 같이 토큰 확인한 후 localStorage에 저장하기

* Vue같은 경우 App.vue에서 다음과 같이 토큰이 존재할 시 해당 토큰으로 user정보를 불러옴으로 써 로그인 상태를 유지

* Controller에 만들어 놓은 selectByToken에 해당하는 함수

* 제외시켜놓은 /user/**에 해당하는 것 빼고는 axios를 요청할 때 다음과 같이 header에 localStorage에 저장해 놓은 토큰 값을 같이 보내기
   <file_sep># 1강. 구구단
* package.json 생성
    ```
    npm init
    ```
    ```
    package name: (typescript-tutorial)
    version: (1.0.0)
    description:
    entry point: (index.js)
    test command:
    git repository:
    keywords:
    author: KSC
    license: (ISC)
    ```
* typescript 패키지 설치
    ```
    npm i typescript
    ```
* lecture.html 생성
    ```
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>구구단</title>
    </head>
    <body>
    <script src="lecture.js"></script>
    </body>
    </html>
    ```
* lecture.ts 생성 (html 오픈했을 때 에러 발생, ts를 인식하지 못함)
    ```
    const a: number = 1;
    console.log('hello javascript');
    console.log('hello typescript');
    ```
* terminal을 cmd로 변경 (구글링)
* typescript 전역 설치
    ```
    npm i -g typescript
    ```
* tsc명렁어로 ts파일을 이용하여 js파일 생성
    ```
    tsc lecture.ts
    ```
* watch를 통해 저장 시 자동으로 js파일 생성
    ```
    tsc lecture.ts -w
    ```
* npx란 : 프로젝트에 설치된 버전으로 명령어 실행 (global을 대체)

# 2강. 숫자야구
* lecture.html 내용 변경
    ```
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>숫자야구</title>
    </head>
    <body>
    <script src="lecture.ts"></script>
    </body>
    </html>
    ```
* tsconfig.json 생성 (typescript 공식 문서에서 설정내용 확인 가능)
    ```
    {
        "compilerOptions": {
            // noImplicit과 strict같은 경우는 규칙을 지키면서 개발을 하기 위해 true로 잡는게 좋음. 그렇지 않을 경우 js와 다를 것이 없음
            "noImplicitAny": true,
            "noImplicitThis": true,
            "noImplicitReturns": true,
            "strict": true,

            // lib는 ES2015(ES6)와 ES2017이 중요한데 매번 등장하는 ES를 모두 등록하는 것이 좋음
            "lib": ["DOM", "ES5", "ES6", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020"],

            // 문서 compile 타겟을 ES5로 잡는다. 보통 ES5나 ES6로 하는데 ES5로 하면 클래스 형태에서 에러가 날 수 있고 ES6로 하면 IE에서 돌아가지 않기 때문에 ES6로 설정하고 babel을 추가하여 사용하기도 한다.
            "target": "ES5"
        },
        "include": ["lecture.ts"], // 해당 tsconfig를 포함시킬 파일
        "exclude": ["*.js"], // 해당 tsconfig를 제외시킬 파일
        // "extends": "" // 공통 tsconfig가 있고 폴더마다 다른 tsconfig를 적용하고 싶을 때 사용
    }
    ```
# 3강. 가위바위보 게임
* lecture.html 변경
    ```
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>rsp</title>
        <style>
            #computer {
                width: 142px;
                height: 200px;
                background-position: 0 0;
            }
        </style>
    </head>
    <body>
    <div id="computer">
        <img src="" alt="">

    </div>
    <div>
        <button id="rock" class="btn">바위</button>
        <button id="scissor" class="btn">가위</button>
        <button id="paper" class="btn">보</button>
    </div>
    <script src="lecture.js"></script>
    </body>
    </html>
    ```
* interface와 type의 차이
    * interface는 서로 분리해서 명시하면 합쳐지지만 type은 그러지 않는다 (해당 기능을 이용하여 다른 사람이 미리 정의한 interface에 나만의 속성값을 추가하여 사용할 수 있다)
        ```
        interface RSP {
            readonly ROCK: '0';
        }

        interface RSP {
            readonly SCISSORS: '-142px';
            readonly PAPER: '-284px';
        }

        type RSP1 = {
            readonly ROCK: '0';
            readonly SCISSORS: '-142px';
            readonly PAPER: '-284px';
        }
        ```
    * interface는 주로 객체에, type은 주로 새로운 타입을 정의할 때 사용
* 객체의 인터페이스가 명확하지 않을 때
    ```
    interface Example {
        a: 3;
        b: 7;
        [key: string]: number;  // key는 string에 값을 number인 어떠한 값이 들어와도 상관없다
    }

    const example: Example = {
        a: 3,
        b: 7,
        d: 10,
        e: 5, // ...
    }
    ```
# 4강. 자스스톤
* lecture.html파일 변경
    ```
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>자스스톤</title>
        <style>
            #my, #rival {
                display: inline-block;
                vertical-align: top;
                margin-right: 50px;
                min-width: 500px;
            }
            .turn {
                border: 1px solid blue;
            }
            #rival {
                vertical-align: bottom;
            }
            #my-deck, #rival-deck {
                display: inline-block;
                vertical-align: top;
                width: 300px;
                background: silver;
            }
            #rival-deck, #rival-hero, #rival-cards, #my-cards, #my-deck, #my-hero {
                text-align: center;
            }
            .card {
                width: 75px;
                height: 120px;
                display: inline-block;
                position: relative;
                border: 1px solid black;
                margin-bottom: 10px;
                background: white;
                user-select: none;
            }
            .card-name {
                text-align: center;
                font-size: 14px;
            }
            .card-att, .card-hp, .card-cost {
                font-size: 16px;
                font-weight: bold;
                text-align: center;
                line-height: 30px;
                width: 30px;
                height: 30px;
                border-radius: 50%;
                display: inline-block;
                position: absolute;
                bottom: 0;
                border: 1px solid black;
            }
            .card-cost {
                bottom: auto;
                top: 0;
                left: 0;
                background-color: blue;
                color: white;
            }
            .card-att {
                left: 0;
                background-color: yellow;
            }
            .card-hp {
                right: 0;
                background-color: red;
                color: white;
            }
            .card-hidden {
                display: none;
            }
            .card-selected {
                border: 2px solid red;
            }
            .card-turnover {
                background: gray;
            }
            #turn-btn {
                float: right;
                position: relative;
                top: -23px;
            }
        </style>
    </head>
    <body>
    <div id="rival">
        <div>코스트: <span id="rival-cost">10</span>/<span>10</span></div>
        <div id="rival-hero"></div>
        <div id="rival-cards" style="height: 150px">

        </div>
    </div>
    <div id="rival-deck">
        <div>덱</div>
    </div>
    <hr />
    <button id="turn-btn">턴넘기기</button>
    <div id="my" class="turn">
        <div id="my-cards" style="height: 150px">

        </div>
        <div id="my-hero">
        </div>
        <div>코스트: <span id="my-cost">10</span>/<span>10</span></div>
    </div>
    <div id="my-deck">
        <div>덱</div>
    </div>
    <div class="card-hidden">
        <div class="card">
            <div class="card-cost"></div>
            <div class="card-att"></div>
            <div class="card-hp"></div>
        </div>
    </div>
    <script src="lecture.js"></script>
    <div>빨강이 체력, 노랑이 공격력, 적 영웅의 체력이 0이되면 승리!
    영웅을 보호하기 위해 쫄병들을 코스트(파란색) 내에서 뽑을 수 있음</div>
    </body>
    </html>
    ```
<file_sep># CentOS 설치
* CentOS 다운로드 사이트 접속 : https://www.centos.org/download
* CentOS Linux DVD ISO 클릭

* 위에 3개 중 아무거나 클릭하여 설치

# Virtual Box 설치
* Virtual Box 다운로드 사이트 접속 : https://virtualbox.org/wiki/Downloads
* Window hosts 클릭하여 설치

# Virtual Box에 CentOS 설치 (총 3개의 OS 생성)
## 가상머신 만들기
* Virtual Box 실행시키기

* 이름, 머신폴더, 종류, 버전 선택

* 메모리 크기 선택 (Cloudera를 설치 할 OS는 8192MB로 나머지는 1024MB로)

* 하드디스크 선택

* 디스크 파일종류 선택

* 할당방식 선택

* 디스크 크기 선택 (모두 40GB로)

## 셋팅 변경
* 설정에 들어가서 포인팅 장치를 USB 태블릿으로 변경

* 설정에 들어가서 어댑터 종류 변경

## OS 설치하기
* 설치받은 iso를 등록하여 가상머신 실행시키기

* Install CentOS Linux 8 선택

* 한국어 선택

* 추가설정 해주기, 우선 KeyBoard 선택

* 영어(미국) 추가하기

* 소프트웨어를 선택하여 워크스테이션 선택

* 네트워크 및 호스트를 선택하여 오른쪽에 이더넷 켜주기

* 설치 시작을 누른 뒤 Root와 User를 클릭하여 생성하기

* 업데이트가 완료되면 재부팅 하기

* 등록한 iso 제거하기

* 강제 마운트 해제 클릭

* 시스템 전원 끄기를 클릭하여 종료하기

* 다시 실행을 시킨 뒤 License Information 클릭

* 약관에 동의한 뒤 완료 클릭

* 설정완료 클릭

* 등록한 계정 클릭

* 등록한 비밀번호 입력

* 등록한 언어 확인 후 다음 클릭

* 가운데 껄로 입력기 선택 후 다음 클릭

* 위치 정보 서비스 여부 선택 후 다음 (저 같은 경우는 해제)

* 연결하고 싶은 계정 연결 (저 같은 경우는 건너뛰기)

* CentOS Linux 시작 클릭

* 필요한 경우 읽고 필요없을 경우 오른쪽 상단의 X를 클릭하면 메인화면 등장하며 설치 완료

# 고정 IP 등록 (터미널에서 수행)
* root계정으로 접속

    ```su - root``` 입력 후 등록한 비밀번호 입력
* 네트워크 설정 폴더로 이동

    ```cd /etc/sysconfig/network-scripts```
* 설정 파일 텍스트 열기 (파일 이름이 다를 수도 있음)

    ```vim ifcfg-enp0s3```
* 텍스트 파일 변경하기

    ```
    TYPE="Ethernet"
    PROXY_METHOD="none"
    BROWSER_ONLY="no"
    #BOOTPROTO="dhcp" --> 변경
    DEFROUTE="yes"
    IPV4_FAILURE_FATAL="no"
    #IPV6INIT="yes" --> 변경
    #IPV6_AUTOCONF="yes" --> 변경
    #IPV6_DEFROUTE="yes" --> 변경
    #IPV6_FAILURE_FATAL="no" --> 변경
    #IPV6_ADDR_GEN_MODE="stable-privacy" --> 변경
    NAME="enp0s3"
    UUID="a63b84el-lb4e-48b3-al87-d3360b0f9025"
    DEVICE="enp0s3"
    ONBOOT="yes"

    BOOTPROTO="static" --> 추가
    IPADDR="192.168.0.121" --> 추가, 아래 입력된 Gateway의 마지막만 값을 변경하여 설정 ex )192.168.0.xxx
    NETMASK="255.255.255.0" --> 추가
    GATEWAY="192.168.0.1" --> 추가, Local PC의 이더넷 Default Gateway로 설정
    DNS1="172.16.17.32" --> 추가, KT DNS 서버 주소
    DNS2="172.16.17.32" --> 추가
    ```
* 네트워크 재시작하기

    ```systemctl restart network```
* 네트워크 재시작이 에러 날 경우

    ```systemctl is-active network``` --> 동작하는지 확인
    active가 아닐 경우 ```yum install network-scripts``` 로 설치
* 재시작을 해서 ip 변경이 일어나지 않을 경우 centOS 종료 후 다시 시작하기

# HostName 영구적으로 변경하기 (터미널에서 수행)
* ```hostnamectl status```를 입력하면 정보 확인 가능, default로 localhost.localdomain 되어 있음
* ```hostnamectl set-hostname {host명}```을 입력하면 root계정 정보 입력 후 변경

# Cloudera 설치 (터미널에서 수행)
* root 계정 접속

    ```su - root``` 후 비밀번호 입력
* Cloudera Manager 설치

    ```wget http://archive.cloudera.com/cm5/redhat/6/x86_64/cm/cloudera-manager.repo```
* repo파일 편집기 켜기

    ```vim cloudera-manager.repo```
* repo 파일 수정

    ```
    [cloudera-manager]
    # Packages for Cloudera Manager, Version 5, on Redhat or CentOS 6 x86_64

    name=Cloudera Manager
    baseurl=https://archive.cloudera.com/cm5/redhat/6/x86_64/cm/5.9.0 --> 수정
    gpgkey=https://archive.cloudera.com/cm5/redhat/6/x86_64/cm/RPM-GPG-KEY-cloudera
    gpgcheck = 1
    ```
* repo 파일 위치 변경

    ```mv ./cloudera-manager.repo /etc/yum.repos.d/```
* jdk 설치

    ```yum install oracle-j2sdk1.7```
* 데몬, 서버 설치

    ```yum -y install cloudera-manager-daemons cloudera-manager-server```
* DB 설치

    ```yum -y install cloudera-manager-server-db-2```
* selinux 편집기 켜기

    ```vim /etc/sysconfig/selinux```
* selinux 파일 수정

    ```
    # This File controls the state of SELinux on the system.
    # SELINUX= can take one of these three values:
    #   enforcing - SELinux security policy is enforced.
    #   permissive - SELinux prints warnings instead of enforcing.
    #   disabled - No SELinux policy is loaded.
    SELIUNX=disabled --> 수정
    # SELINUXTYPE= can take one of these three values:
    #   targeted - Targeted processes are protected.
    #   minimum - Modification of targeted policy. Only selected processes are protected.

    #   mls - Multi Level Security protection.
    SELINUXTYPE=targeted
    ```
* 전원 종료 후 재시작 및 root계정 접속
* DB 실행

    ```service cloudera-scm-server-db start```
* 서버 실행

    ```service cloudera-scm-server start```
* 서버 실행 확인

    ```service --status-all```<file_sep>interface obj<T extends string | number> {
    add: (a: T, b: T) => T;
}

const a: obj<number> = {
    add: (a, b) => {
        return a + b;
    },
};

const b: obj<string> = {
    add: (a, b) => {
        return a + b;
    },
};

function forEach<T>(arr: T[], callback: (item: T) => void): void {
    for (let i: number = 0; i < arr.length; i++) {
        callback(arr[i]);
    }
}

forEach<string>(['a', 'b', 'c'], (item) => {});

interface Card {
    att: number;
    hp: number;
    mine: boolean;
    field: boolean;
    cost?: number;
    hero?: boolean;
}

class Hero implements Card {
    att: number;
    hp: number;
    mine: boolean;
    field: boolean;
    hero: boolean;
    constructor(mine: boolean) {
        this.mine = mine;
        this.att = Math.ceil(Math.random() * 2);
        this.hp = Math.ceil(Math.random() * 5) + 25;
        this.hero = true;
        this.field = true;
    }
}

class Sub implements Card {
    att: number;
    hp: number;
    mine: boolean;
    field: boolean = false;
    cost: number;
    constructor(mine: boolean) {
        this.mine = mine;
        this.att = Math.ceil(Math.random() * 5);
        this.hp = Math.ceil(Math.random() * 5);
        this.cost = Math.floor((this.att + this.hp) / 2);
    }
}

interface Player {
    hero: HTMLDivElement;
    deck: HTMLDivElement;
    field: HTMLDivElement;
    cost: HTMLDivElement;
    deckData: Card[];
    heroData?: Card | null;
    fieldData: Card[];
    chosenCard?: HTMLDivElement | null;
    chosenCardData?: Card | null;
}

const opponent: Player = {
    hero: document.getElementById('rival-hero') as HTMLDivElement,
    deck: document.getElementById('rival-deck') as HTMLDivElement,
    field: document.getElementById('rival-cards') as HTMLDivElement,
    cost: document.getElementById('rival-cost') as HTMLDivElement,
    deckData: [],
    heroData: null,
    fieldData: [],
    chosenCard: null,
    chosenCardData: null,
};

const me: Player = {
    hero: document.getElementById('my-hero') as HTMLDivElement,
    deck: document.getElementById('my-deck') as HTMLDivElement,
    field: document.getElementById('my-cards') as HTMLDivElement,
    cost: document.getElementById('my-cost') as HTMLDivElement,
    deckData: [],
    heroData: null,
    fieldData: [],
    chosenCard: null,
    chosenCardData: null,
};
<file_sep>
public class Programmers_거스름돈 {
	public static void main(String[] args) {
		System.out.println(solution(5, new int[]{1,2,5}));
	}

	public static int solution(int n, int[] money) {
        int res[] = new int[n+1];
        res[0] = 1;

        for(int j=0; j<money.length; j++) { // j => j번째 화폐
            for(int i=0; i<=n; i++) { // i => 만들려는 금액
                if(i >= money[j]) { // 만들려는 금액이 j번째 화폐보다 커야 j번째 화폐를 이용하여 금액을 만들 수 있음
                    res[i] = res[i] + res[i-money[j]];
                }
            }
        }

        return res[n];
    }
}

/*
화폐를 하나씩 돌아가면서 0원부터 n원까지 만드는 경우의 수를 계속 더해주는 방법.
예시에서 첫 번째 화폐인 1원으로 0원부터 5원까지 만들고
두 번째 화폐인 2원으로 0원부터 5원까지 만들고
마지막 화폐인 5원으로 0원부터 5원까지 만든다.
1원으로 만들어지는 경우는 : 0=>x / 1=>{1} / 2=>{1,1} / 3=>{1,1,1} / 4=>{1,1,1,1} / 5=>{1,1,1,1,1}
누적하여 2원으로 만들어지는 경우는 : 0=>x / 1=>{1} / 2=>{1,1}, {2} / 3=>{1,1,1}, {1,2} / 4=>{1,1,1,1}, {1,1,2}, {2,2} / 5=>{1,1,1,1,1}, {1,1,1,2}, {1,2,2}
누적하여 5원으로 만들어지는 경우는 : 0=>x / 1=>{1} / 2=>{1,1}, {2} / 3=>{1,1,1}, {1,2} / 4=>{1,1,1,1}, {1,1,2}, {2,2} / 5=>{1,1,1,1,1}, {1,1,1,2}, {1,2,2}, {5}
*/<file_sep>let imgCoords: RSP[keyof RSP] = '0';

type RSP1 = {
    readonly ROCK: '0';
    readonly SCISSORS: '-142px';
    readonly PAPER: '-284px';
}; // 주로 타입을 새로 생성할 때 사용

interface RSP {
    readonly ROCK: '0';
} // 객체에 해당

interface RSP {
    readonly SCISSORS: '-142px';
    readonly PAPER: '-284px';
}

interface Example {
    a: 3;
    b: 7;
    [key: string]: number;
}

const example: Example = {
    a: 3,
    b: 7,
    d: 100,
};

const rsp: RSP = {
    ROCK: '0',
    SCISSORS: '-142px',
    PAPER: '-284px',
};

const score = {
    ROCK: 0,
    SCISSORS: 1,
    PAPER: -1,
} as const;

function computerChoice(imgCoords: RSP[keyof RSP]): keyof RSP {
    return (Object.keys(rsp) as ['ROCK', 'SCISSORS', 'PAPER']).find((k) => rsp[k] === imgCoords)!;
}

let interval: number;
let point: number = 0;

document.querySelectorAll('.btn').forEach((btn) => {
    btn.addEventListener('click', function (this: HTMLButtonElement, e: Event) {
        clearInterval(interval);
        setTimeout(intervalMarker, 2000);
        const myChoice = this.textContent as keyof RSP;
        const myScore = score[myChoice];
        const computerScore = score[computerChoice(imgCoords)];
        const diff = myScore - computerScore;
        if (diff === 0) {
            console.log('비겼습니다.');
        } else if ([-1, 2].includes(diff)) {
            console.log('이겼습니다!!');
            point++;
        } else {
            console.log('졌습니다!!');
            point--;
        }
        (document.querySelector('#point') as HTMLDivElement)!.textContent = String(point);
    });
});

function intervalMarker() {
    interval = setInterval(function () {
        if (imgCoords === rsp.ROCK) {
            imgCoords = rsp.SCISSORS;
        } else if (imgCoords === rsp.SCISSORS) {
            imgCoords = rsp.PAPER;
        } else {
            imgCoords = rsp.ROCK;
        }

        if (document.querySelector('#computer')) {
            (document.querySelector(
                '#computer',
            ) as HTMLDivElement).style.background = `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoords} 0`;
        }
    }, 100);
}

intervalMarker();
<file_sep># OS

## 프로세스 (Process)
### 프로세스란 ?
* 일반적으로 CPU에 의해 처리되는 사용자 프로그램, 즉 실행중인 프로그램을 의미하며 작업 또는 Task라고도 한다.

* 여러 형태로의 정의
  * 실행중인 프로그램
  * 비동기적 행위
  * 실행중인 프로시저
  * PCB를 가진 프로그램
    * PCB(Process Control Block): 특정 프로세스를 제어할 때 필요한 상태 정보를 포함하는 운영체제 커널의 자료구조 / 프로세스가 생성되면 메모리에 PCB를 생성하고 프로세스가 실행을 종료하면 PCB도 삭제된다.
  * 실기억장치에 저장된 프로그램

* 프로세스 구조

* 프로세스 상태변화

## 스레드 (Thread)
### 스레드란 ?
* 프로세스 내에서 실행되는 흐름의 단위 / 일반적으로 하나의 프로그램은 하나의 스레드를 가지고 있지만 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있음 (멀티스레딩이라고 부름)

### 스레드의 특징
* 스택을 스레드마다 독립으로 할당: 독립적인 실행 흐름을 추가하기 위해
* PC Register를 스레드마다 독립적으로 할당: PC(Program Counter)값은 스레드가 명령어의 주소를 기억하는 역할을 담당 / CPU를 할당받았다가 스케줄러에 의해 선점당하기 때문에 연속적으로 수행되지 못해 어느 부분까지 수행했는지 기억할 필요가 있음

### 멀티스레딩의 장점
* 응답성: 다른 스레드가 작업을 수행하더라도 프로그램 수행이 계속되어 사용자에 대한 응답성 증가
* 자원공유: 같은 주소공간 내에 여러 개의 다른 활동성 스레드를 가질 수 있음
* 경제성: 프로세스를 생성하는 것보다 스레드를 생성하고 문맥교환을 하는 편이 보다 경제적
* 멀티프로세서 활용: 단일 스레드는 CPU가 많아도 CPU 한개에서만 실행되지만 멀티스레딩은 멀티프로세서 구조에서 병렬로 수행 가능

### 멀티스레딩의 단점
* 데이터와 힙영역을 공유하기 때문에 다른 스레드에서 사용중인 변수나 자료구조에 접근하여 엉뚱한 값을 읽거나 수정할 수 있음
* 병목현상으로 인해 단일 스레드의 실행시간보다 오히려 지연될 수 있음

## 스케줄링 (Scheduling)
### 스케줄링이란 ?
* 다중 프로그래밍을 가능하게 하는 운영체제의 동작 기법 중 하나이다.
* 프로세스들에게 CPU 등의 자원 배정을 적절히 하여 시스템의 성능을 개선한다.

### 비선점 스케줄링
* 이미 할당된 CPU를 다른 프로세스가 강제로 빼앗을 수 없는 스케줄링 기법
  * FCFS: 프로세스가 도착한 순서대로 CPU를 할당하는 기법
  * SJF: 실행시간이 가장 짧은 프로세스에게 먼저 CPU를 할당하는 기법
  * HRN: 우선순위를 계산하여 가장 높은 것부터 CPU를 할당하는 기법
    * 우선순위 = (대기시간 + 실행시간) / 실행시간

### 선점 스케줄링
* 하나의 프로세스가 CPU를 할당받아 실행하고 있을 때 다른 프로세스가 CPU를 강제로 빼앗아 사용할 수 있는 스케줄링 기법
  * SRT: 새로 도착한 프로세스의 실행시간을 비교하여 가장 짧은 프로세스에게 CPU를 할당하는 기법
  * Round-Robin: 시분할 시스템으로 고안된 방식 / 시간할당량만 실행한 후 다음 프로세스에게 CPU를 넘겨주고 준비상태의 맨 뒤로 배치
  ```
  time-slice: 4초
  시간      4   8   12   16   20   24   28   30   34   37   38
  P1(15)    4            8              12             15(end)
  P2(10)        4              8             10(end)
  P3(13)            4               8             12        13(end)
  ```
  * 다단계 큐 스케줄링
  * 다단계 피드백 큐 스케줄링

## 프로세스 동기화 (Process Synchronization)
### 프로세스 동기화란 ?
* 공유된 자원들을 프로세스가 사용할 때 발생되는 문제를 제어하기 위해 하나의 자원은 하나의 프로세스만 사용할 수 있도록 하는 것

### 임계 영역(Critical Section)
* 프로세스가 자원을 할당받았을 때 작업을 실행하는 곳
* 임계 영역의 문제 해결을 위한 기본 조건
  * 상호배제(Mutual Exclusion)
    * 어떤 프로세스가 임계 영역에서 자원을 할당 받아 실행중일 때 다른 프로세스는 해당 임계 영역에 진입할 수 없음
  * 진행(Progress)
    * 임계 영역에서 실행중인 프로세스가 없다면 임계 영역으로 진입하려는 프로세스들 중 하나는 유한한 시간 내에 진입이 가능
  * 한정된 대기(Bounded Waiting)
    * 한 프로세스가 임계 영역에 진입을 요청한 후에는 다른 프로세스의 임계 영역 진입을 유한한 횟수로 제한 (무한히 기다리지 않음)

### 동기화 기법
* 뮤텍스(Mutex)
  * 스레드의 동기화 기법으로 공유된 자원에 여러 스레드가 동시에 접근하는 것을 막음
* 세마포어(Semaphore)
  * 프로세스의 동기화 기법으로 공유된 자원에 여러 프로세스가 동시에 접근하는 것을 막음
* 뮤텍스 VS 세마포어
  * 뮤텍스는 공유자원 1개로 한정되어 있고 세마포어는 공유자원을 여러 개로 조절을 할 수 있음
  * 대표적인 예로 음식점 화장실이 있음
  * 뮤텍스는 음식점에 화장실이 1개여서 누군가가 키를 가지고 화장실을 사용하고 있으면 키를 가지고 나올 때 까지 기다려야 됨
  * 세마포어는 음식점에 화장실이 3개(여러 개)여서 한 명씩 화장실을 사용할 때마다 키가 하나씩 없어짐, 화장실을 모두 사용하고 있다면 대기하고 있다가 먼저 키를 가지고 나오는 화장실을 사용함
  * 세마포어는 뮤텍스가 될 수 있지만 뮤텍스는 세마포어가 될 수 없음
  * 뮤텍스 같은 경우는 뮤텍스를 소유하고 있는 쓰레드만 뮤텍스가 해제가 가능, 세마포어는 세마포어를 소유하고 있지 않는 쓰레드도 세마포어 해제 가능

### 데드락(DeadLock) = 교착상태
* 한정된 자원을 여러 곳에서 사용하려고 할 때 발생하는 문제
* 데드락이 발생하는 경우

  * A 프로세스가 a라는 자원을 사용하다가 b라는 자원을 사용하는 상황이 발생
  * b 라는 자원은 B 프로세스가 이미 사용하고 있기에 A 프로세스는 자원을 사용할 수 있을 때까지 대기
  * B 프로세스도 a라는 자원을 사용하려고 하지만 A 프로세스가 자원을 아직 소유하고 있기 때문에 B 프로세스도 자원을 사용할 수 있을 때까지 대기
  * 결과적으로 모두 실행 상태로 변경 되지 못하며 교착 상태가 만들어짐
* 데드락의 발생 조건
  * 상호배제(Mutual Exclusion): 자원은 한 번에 하나의 프로세스만 사용할 수 있음
  * 점유대기(Hold and Wait): 최소한 하나의 자원을 점유하고 있으면서 다른 자원을 추가로 점유하기 위해 대기하는 프로세스가 있어야 한다.
  * 비선점(No Preemption): 다른 프로세스에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없음
  * 순환대기(Circular Wait): 각 프로세스는 순환적으로 다음 프로세스가 요구하는 자원을 소유하고 있음, {P0, P1, ..., Pn}이라는 프로세스 집합이 있을 때 P0은 P1이 점유한 자원을 대기하고 P1은 P2가 점유한 자원을 대기하는 방식 (Pn-1은 Pn이 점유한 자원을 대기, Pn같은 경우는 P0가 점유한 자원을 요구)
* 데드락 해결방법
  * 예방: 데드락 발생 조건(상호배제, 점유대기, 비선점, 순환대기) 중 하나를 제거함으로 써 해결, 자원의 낭비가 심함
  * 회피: 데드락이 일어나는 경우를 피해가는 방법 (은행원 알고리즘이 대표적(Banker's Algorithm))
  * 탐지: 자원할당 그래프를 통해 데드락을 탐지
  * 회복: 데드락을 일으킨 프로세스를 종료하거나 할당된 자원을 해제함으로써 회복
    * 프로세스 종료방법: 데드락 된 프로세스를 모두 중지 -> 데드락이 제거될 때까지 프로세스를 하나씩 중지<file_sep># CRA(Create React App)을 사용하지 않고 환경 설정
## 기본 설치
* Node.js 설치
  * https://nodejs.org/ko/ 로 접속 or 검색창에 Node.js 검색
* yarn 설치
  * VS Code 상단바에 있는 Terminal을 눌러 New Terminal or 키보드로 ctrl + shift + ` 입력
  * terminal에서 npm install -g yarn 입력
## Extension 설정
* ES7 React/Redux/GraphQL/React-Native snippets : 자주 사용하는 snippet들을 단축키로 간단히 생성
* Bracket Pair Colorizer : 여는 대괄호와 닫는 대괄호를 같은 색으로 표시
* Auto Rename Tag : 마크업 앞 혹은 뒤를 수정하면 그에 대응하는 마크업으로 자동 수정
* Prettier - Code formatter : 선호하는 포맷에 따라 커스터마이징하여 사용
* Live Server : 로컬 개발 서버를 생성
## 기본 설정
* root 폴더 생성 후 terminal에 해당 폴더까지 들어가기
* package.json 생성 : 노드 프로젝트에 대한 정보를 담음
  ```
  npm init
  ```
* 내용 입력
  ```
  package name: {패키지 명} ex) video_chat
  version: {버전} ex) (1.0.0)
  description: {세부 설명} ex) video chat visual
  entry point: {시작 지점} ex) (index.js)
  test command:
  git repository:
  keywords:
  author: {제작자} ex) KSC
  license: {라이센스} ex) (ISC)
  ```
* react 사용을 위한 모듈 설치
  ```
  npm install react react-dom
  ```
* root폴더에 public 폴더 생성 후 index.html 생성
* index.html에 내용 입력
  ```
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>Document</title>
    </head>
    <body>
      <div id="root"></div>
    </body>
  </html>
  ```
* root폴더에 src 폴더 생성 후 App.js 생성
* App.js에 내용 입력
  ```
  import React from 'react';

  const App = () => {
      return <div>hello world!</div>
  }

  export default App;
  ```
* src폴더에 index.js 생성
* index.js에 내용 입력
  ```
  import React from 'react';
  import ReactDOM from 'react-dom';
  import App from './App';

  ReactDOM.render(<App />, document.querySelector("#root"));
  ```
* 바벨에 필요한 모듈 설치 : 브라우저가 이해할 수 있는 자바스크립트의 최신 문법으로 변환 (ES5 -> ES6)
  ```
  npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader
  ```
  * @babel/core : 바벨을 사용하기 위한 필수 모듈
  * @babel/preset-env : 바벨에서 스크립트 코드를 트랜스 파일링 하기 위한 플러그인들을 모아둔 모듈
  * @babel/preset-react : 바벨에서 리액트 코드를 트랜스 파일링 하기 위한 플러그인들을 모아둔 모듈
  * babel-loader : 웹팩을 돌릴 시에 바벨을 적용하기 위한 모듈
* root폴더에 babel.config.js 파일 생성
* babel.config.js에 내용 입력
  ```
  module.exports = function(api) {
    api.cache(true);

    const presets = ["@babel/preset-env", "@babel/preset-react"];

    const plugins = [];

    return {
      presets,
      plugins
    };
  };
  ```
* 웹팩에 필요한 모듈 설치 : 모든 리액트 관련 파일들을 브라우저에서 이용할 수 있는 하나의 번들로 묶어 패킹하는 모듈 번들러
  ```
  npm i -D webpack webpack-cli html-webpack-plugin
  ```
  * webpack : 웹팩 모듈 (모든 리액트 파일을 하나의 컴파일된 자바스크립트 파일로 생성)
  * webpack-cli : 웹팩 명령어를 커맨드 라인에서 실행할 때 필요한 모듈
  * html-webpack-plugin : 웹팩 실행이 완료된 번들 파일을 붙인 html 파일을 만들어 주는 모듈
* root폴더에 webpack.config.js 파일 생성
* webpack.config.js에 내용 입력
  ```
  const path = require("path");
  const HtmlWebpackPlugin = require("html-webpack-plugin");

  module.exports = {
    mode: "development",

    entry: "./src/index",

    resolve: {
      extensions: [".js", ".jsx"]
    },

    module: {
      rules: [
        {
          test: /\.jsx?$/,
          loader: "babel-loader",
          exclude: ['/node_modules/']
        }
      ]
    },

    output: {
      path: path.resolve(__dirname, "dist"), // __dirname : 현재 디렉토리
      filename: "bundle.js",
      publicPath: '/', // 새로고침을 할 시 페이지 유지
    },

    plugins: [
      new HtmlWebpackPlugin({
        // index.html에 output에서 만들어진 bundle.js를 적용하여, dist에 새로운 html 파일 생성
        template: `./public/index.html`
      })
    ]
  };
  ```
  * path : core nodejs 모듈 중 하나로 파일 경로 설정할 때 사용
  * html-webpack-plugin : index.html 파일을 dist폴더에 bundle 파일과 함께 자동으로 생성
  * mode : production or development
  * entry : 파일들을 묶기 위해 웹팩이 바라보는 파일 시작점
  * resolve : 웹팩이 자동으로 경로나 확장자를 처리할 수 있게 도와주는 옵션
  * output: bundle된 파일의 결과물을 위한 설정
* 웹팩에 필요한 모듈 추가 설치
  ```
  npm i -D webpack-dev-server
  ```
  * webpack-dev-server : 변경사항이 있을 때 마다 자동으로 webpack을 수정해주는 역할, npm start할 시 자동으로 페이지 로드 수행
* package.json에 설정 추가
  ```
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --open" // 추가
  },
  ```
* webpack.config.js 설정 추가
  ```
  devServer: {
    historyApiFallback: true, // 새로고침을 할 시 페이지 유지
    contentBase: path.join(__dirname, "dist"), // 이 경로에 있는 파일이 변경될 때 번들을 다시 컴파일
    compress: true,
    port: {port명} ex) 8888
  },
  ```
# ESLint, Prettier 적용하기
## ESLint 설정
* ESLint를 위한 필수 패키지 설치
  ```
  yarn add --dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
  ```
* root폴더에 .eslintrc 파일 생성
* .eslintrc에 내용 입력
  ```
  {
    "env": {
      "browser": true,
      "es6": true
    },
    "extends": ["airbnb", "prettier", "prettier/react"],
    "globals": {
      "Atomics": "readonly",
      "SharedArrayBuffer": "readonly"
    },
    "plugins": ["react", "prettier"],
    "rules": {
      "react/prop-types": 0,
      "no-underscore-dangle": 0,
      "import/imports-first": ["error", "absolute-first"],
      "import/no-extraneous-dependencies": [
        "error",
        {
          "devDependencies": true
        }
      ],
      "import/newline-after-import": "error",
      "import/prefer-default-export": 0,
      "react/prefer-stateless-function": 0,
      "react/jsx-filename-extension": 0,
      "react/jsx-one-expression-per-line": 0,
      "linebreak-style": 0,
      "no-unused-vars": 0,
      "camelcase": 0

      // error off : 0 , warning 1,
    }
  }
  ```
* 설정 창 오픈 (ctrl + ,)
* 오른쪽 상단에 3개 아이콘 중 맨 왼쪽 클릭 (파일모양)
* setting.json에 내용 입력
  ```
  "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true,
  },
  ```
  * editor.codeActionsOnSave : 저장할 때 속성값 수행
  * source.fixAll.eslint : ESLint에 정해진 대로 코드 자동 수정
## Prettier 설정
* Prettier를 위한 필수 패키지 설치
  ```
  yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier babel-eslint
  ```
* 설정 창 오픈 (ctrl + ,)
* 오른쪽 상단에 3개 아이콘 중 맨 왼쪽 클릭 (파일모양)
* setting.json에 내용 입력
  ```
  {
    "[javascript]": {
        "editor.formatOnSave": true
    },
    "[javascriptreact]": {
        "editor.formatOnSave": true
    },
    "prettier.singleQuote": true,
    "prettier.semi": true,
    "prettier.tabWidth": 4,
    "prettier.trailingComma": "all",
    "prettier.printWidth": 120,
    "prettier.arrowParens": "always"
  }
  ```
  * singleQuote : 문자열에 싱글쿼트(') 사용
  * semi : 코드 끝에 세미클론(;) 사용
  * tabWidth : 들여쓰기 4칸
  * trailingComma : 객체나 배열 작성할 때 값 맨 뒤에거에도 쉼표(,) 사용
  * printWidth : 한 줄에 120칸 까지 작성
  * arrowParens : 화살표 함수 매개변수 하나인건 괄호 사용 안함 (const test = variable => {})
# Axios 적용하기
## 기본 설정
* axios 설치하기
  ```
  yarn add axios
  ```
## baseURL 등록하기
* src/api에 axios.js 파일 생성
* axios.js에 내용 입력
  ```
  import axios from 'axios';

  const axiosAPI = axios.create({
      baseURL: 'http://localhost:8080/videochat', // axios 사용할 때 path앞에 baseURL이 항상 추가
  });

  export default axiosAPI;
  ```
## 사용방법
* 회원가입 예제
  ```
  import React from 'react';
  import axios from '../api/axios';

  const signUp = () => {
      const userSignUp = () => {
          let user = {
              user_id: '123',
              user_pw: '456',
              user_nm: '789',
              user_gd: 'M',
              user_age: 30,
              user_ph: '010-1234-5678',
              user_ma: '<EMAIL>',
          };

          axios
              .post('/user/insert', user)
              .then((res) => {
                  if (res.data.data == 1) {
                      console.log('회원가입 성공!');
                  }
              })
              .catch((e) => {
                  console.log(e);
              });
      };

      return (
          <>
              <p>HELLO</p>
              <input type="button" value="signUp" onClick={userSignUp} />
          </>
      );
  };

  export default signUp;
  ```
# router 적용하기
## 기본설정
* router를 위한 필수 패키지 설치
  ```
  yarn add react-router react-router-dom
  ```
## 사용방법
* App.js 파일 수정
  ```
  import React from 'react';
  import { Redirect, Route } from 'react-router'; // Redirect: 페이지 Redirect, Route: path에 알맞은 component 등록
  import { BrowserRouter } from 'react-router-dom'; // Route 외부에 덮어져서 Browser 이동을 도움
  import SignIn from './pages/signIn';
  import SignUp from './pages/signUp';

  const App = () => {
      return (
          <>
              <BrowserRouter>
                  <Route exact path="/">
                      <Redirect to="/user/signIn" />
                  </Route>
                  <Route path="/user/signIn" component={SignIn} />
                  <Route path="/user/signUp" component={SignUp} />
              </BrowserRouter>
          </>
      );
  };

  export default App;
  ```
* Link 사용하기 (a태그와 유사)
  ```
  <Link to="/user/signUp">signUp</Link>
  ```
* useHistory 사용하기 (window.history와 유사)
  ```
  import React from 'react';
  import { useHistory } from 'react-router';

  const signIn = () => {
    const history = useHistory();

    const onSignUp = () => {
        history.push('/user/signUp');
    };

    return (
      <>
        <div>
            <input type="button" value="SignUp" onClick={onSignUp} />
        </div>
      </>
    )
  }

  export default signIn;
  ```

# redux 사용 방법
## 기본 설정
* redux devtools 크롬 확장자 설치
  * https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko
* redux에 필요한 모듈 설치
  ```
  yarn add redux react-redux@next redux-devtools-extension
  ```
* src에 modules폴더 생성
* src/modules/reducer에 index.js 파일 생성
* index.js에 내용 입력
  ```
  import { combineReducers } from 'redux';

  const rootReducer = combineReducers({
  });

  export default rootReducer;
  ```
* src/modules에 actions.js 파일 생성
* actions.js에 내용 입력
  ```
  const user_setUser = 'user_setUser';

  export const setUserAction = res => {
      return {
          type: user_setUser,
          payload: res,
      }
  }
  ```
* src/modules/reducer에 user.js 파일 생성
* user.js에 내용 입력
  ```
  import * as actions from '../actions';

  export const initState = {
    user: {},
  }

  const reducer = (state = initState, action) => {
      switch(action.type) {
          case actions.user_setUser: {
              return {
                  ...state,
                  user: action.payload,
              };
          }

          default: {
              return {
                  ...state,
              };
          }
      }
  }

  export default reducer;
  ```
* src/modules/reducer/index.js에 내용 추가
  ```
  import user from './user';

  const rootReducer = combineReducers({
      user,
  });
  ```
* src/modules에 store.js 파일 생성
* store.js에 내용 추가
  ```
  import { createStore } from 'redux';
  import { composeWithDevTools } from 'redux-devtools-extension';
  import rootReducer from './reducer';

  const store = createStore(rootReducer, composeWithDevTools());

  export default store;
  ```
* src의 index.js파일 설정 추가
  ```
  import React from 'react';
  import ReactDOM from 'react-dom';
  import { Provider } from 'react-redux';
  import App from './App';
  import store from './modules/store';

  ReactDOM.render(
      <Provider store={store}>
          <App />
      </Provider>,
      document.querySelector('#root'),
  );
  ```
* src/pages폴더 생성
* src/pages폴더에 login.js파일 생성
* login.js에 내용 입력 (button을 클릭하면 redux에 데이터 저장 후 saveuser에 데이터 연동되어 이름 표시)
  ```
  import React from 'react';
  import { useDispatch, useSelector } from 'react-redux';
  import { setUserAction } from '../modules/actions';

  const login = () => {
      const dispatch = useDispatch(); // redux에 데이터를 전송하여 저장하는 역할 수행
      const saveuser = useSelector(state => state.user.user); // state.{rootReducer에 등록된 user파일}.{user파일의 initState의 user객체}의 값을 saveuser에 저장

      const setUser = () => {
          const user = {
              id: 'abc',
              name: 'person',
              phone: '010-1234-5678',
          }
          dispatch(setUserAction(user));
      }

      return (
          <div>
              <h1>Login 페이지 입니다.</h1>
              <input type="button" value="button" onClick={setUser}></input>
              <p>나의 이름은 {saveuser.name}</p>
          </div>
      )
  }

  export default login;
  ```
# react hook 사용 방법
* useState 사용 방법
  ```
  const [userId, setUserId] = useState('');

  const onUserId = (e) => {
    setUserId(e.target.value);
  };

  return (
    <div>
        <h1>SCVC Login</h1>
        <input type="text" onChange={onUserId} />
    </div>
  );
  ```
# react 전용 UI-Framework 사용하기
## Material-UI 사용하기 (진행하던 시즌 중 1위...)
* https://material-ui.com/ 에 접속하면 관련 정보를 얻을 수 있음
* material 패키지 설치하기
  ```
  yarn add @material-ui/core
  ```

# redux-saga 사용하기
## 기본 설정
* 필수 패키지 설치
  ```
  yarn add redux-saga
  ```
* babel.config.js 내용 추가 // async, await 사용 위함
  ```
  module.exports = function (api) {
      api.cache(true);

      // preset-env에 targets 추가
      const presets = [
          [
              '@babel/preset-env',
              {
                  targets: {
                      node: true,
                  },
              },
          ],
          '@babel/preset-react',
      ];

      const plugins = [];

      return {
          presets,
          plugins,
      };
  };
  ```
* src/modules/saga index.js 파일 생성
* index.js에 내용 입력
  ```
  import { all } from 'redux-saga/effects';

  function* rootSaga() {
      yield all();
  }

  export default rootSaga;

  ```
* src/modules/saga user.js 파일 생성
* user.js에 내용 입력
  ```
  import * as actions from '../actions';
  import axios from '../../api/axios';

  const saga = [];

  export default saga;
  ```
* src/modules/store.js 내용 변경
  ```
  import { createStore, applyMiddleware } from 'redux'; // applyMiddleware 추가
  import { composeWithDevTools } from 'redux-devtools-extension';
  import createSagaMiddleware from 'redux-saga'; // 추가
  import rootReducer from './reducer';
  import rootSaga from './saga'; // 추가

  const sagaMiddleware = createSagaMiddleware(); // 추가
  const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware))); // DevTools 괄혼안에 applyMiddleware(sagaMiddleware) 추가

  sagaMiddleware.run(rootSaga); // 추가

  export default store;
  ```
## 예시 (로그인을 할 때 saga를 이용하여 비동기 처리하기)
* 처리되는 로직
  * saga가 반응하길 기다리고 있는(takeEvery) type을 dispatch(userSelectByUserIdAction)
  * reducer에서 해당 type에 반응(actions.userSelectByUserId)
  * saga가 해당 type을 가로채어 데이터 처리 후(userSelectByUserIdSaga) saga가 기다리지 않는 type에 put(actions.setUserAction(res.data.data))
  * reducer가 해당 type에 반응하여 데이터를 저장(actions.userSetUser)
* src/modules/action.js에 내용 추가
  ```
  // Saga
  export const userSelectByUserId = 'userSelectByUserId';

  export const userSelectByUserIdAction = (res) => {
      return {
          type: userSelectByUserId,
          payload: res,
      };
  };
  ```
* src/modules/reducer/user.js에 내용 추가
  ```
  import * as actions from '../actions';

  const initState = {
      user: '',
  };

  const reducer = (state = initState, action) => {
      switch (action.type) {
          // Saga
          case actions.userSelectByUserId: {
              return {
                  ...state,
                  payload: action.payload,
              };
          }

          // Not Saga
          case actions.userSetUser: {
              return {
                  ...state,
                  user: action.payload,
              };
          }

          default: {
              return {
                  ...state,
              };
          }
      }
  };

  export default reducer;
  ```
* src/modules/saga/user.js에 내용 추가
  ```
  import { takeEvery, put, call } from 'redux-saga/effects';
  import * as actions from '../actions';
  import axios from '../../api/axios';

  function* userSelectByUserIdSaga(action) {
      try {
          const res = yield call([axios, 'get'], '/user/selectByUserId', {
              params: {
                  userId: action.payload.userId,
                  userPw: action.payload.userPw,
              },
          });

          if (res.data.data === 0) {
              alert('로그인 실패');
          } else {
              yield put(actions.setUserAction(res.data.data));
              localStorage.userToken = res.headers['jwt-user-token']; // jwt-user-token으로 response온 값을 localStorage에 저장
              alert('로그인 성공');
          }
      } catch (e) {
          alert(e);
      }
  }
  const saga = [
      takeEvery(actions.userSelectByUserId, userSelectByUserIdSaga),
  ];

  export default saga;
  ```
* 로그인 페이지에서 로그인 버튼을 누를 때 함수
  ```
  const onSignIn = () => {
      dispatch(userSelectByUserIdAction({ userId, userPw }));
  };
  ```<file_sep># Android

## NDK (Native Development Kit)
### NDK 란 ?
* C와 C++ 같은 네이티브 코드언어를 사용하여 앱의 일부를 구현할 때 사용되는 kit
* NDK를 사용하기 위해서는 JNI(Java Native Interfact)가 사용해야 됨
### 장점
* 속도향상
* 유연한 프로그램 개발
<file_sep># personal-study
## goal
### 1. Algorithm(Programmers, Baekjoon)
### 2. Concept(OS, Network etc)
### 3. FrontEnd(React)<file_sep># npx create-next-app 사용하지 않고 환경설정 하기
## 기본파일 구조 생성
* package.json 생성
    ```
    npm init
    ```
* package.json scripts 수정
    ```
    "scripts": {
        "dev": "next dev -p 8888", // webpack dev-server 로 8888포트 오픈
        "build": "next build", // build
        "start": "next start" // build파일로 실행
    },
    ```
* 필수 패키지 설치
    ```
    npm install next react react-dom
    ```
* root폴더 위치에 pages폴더 생성
* /pages/index.js 파일 생성 및 작성
    ```
    import * as React from 'react';

    const Index = () => {
        return (
            <>
                <div>
                    index page!!
                </div>
            </>
        );
    };

    export default Index;
    ```
* /pages/_app.js 파일 생성 및 작성
    ```
    const App = ({ Component, pageProps }) => {
        return <Component {...pageProps} />;
    };

    export default App;
    ```
* /pages/__error.js 파일 생성 및 작성
    ```
    import * as React from 'react';

    const Error = ({ statusCode }) => {
        return (
            <div>
                에러 발생 시 나오는 페이지 <br /> {statusCode}
            </div>
        );
    };

    export const getServerSideProps = async ({ res, req }) => {
        const statusCode = res.statusCode;

        return {
            props: {
                statusCode,
            },
        };
    };

    export default Error;
    ```
* /pages/404.js 파일 생성 및 작성 (에러에 맞는 페이지를 작성 가능)
    ```
    import * as React from 'react';

    const Error404 = () => {
        return (
            <>
                <div>
                    <p>404 에러 페이지</p>
                </div>
            </>
        );
    };

    export default Error404;
    ```
* 실행
    ```
    npm run dev
    ```

## TypeScript 적용
* 필수 패키지 설치
    ```
    npm install typescript @types/react @types/node
    ```
* root폴더에 next-env.d.ts파일 생성
* /pages/_app.js -> /pages/_app.tsx 변경 및 내용 수정
    ```
    import { AppProps } from 'next/app';

    const App = ({ Component, pageProps }: AppProps) => {
        return <Component {...pageProps} />;
    };

    export default App;
    ```
* /pages/_error.js -> /pages/_error.tsx 변경 및 내용 수정
    ```
    import * as React from 'react';
    import { GetServerSideProps } from 'next';

    interface IError {
        statusCode: number;
    }

    const Error: React.FC<IError> = ({ statusCode }): JSX.Element => {
        return (
            <div>
                에러 발생 시 나오는 페이지 <br /> {statusCode}
            </div>
        );
    };

    export const getServerSideProps: GetServerSideProps = async ({ res, req }) => {
        const statusCode = res.statusCode;

        return {
            props: {
                statusCode,
            },
        };
    };

    export default Error;
    ```
* 다른 js파일 -> tsx파일로 변경
* npm run dev 실행 시 추천되는 tsconfig.json 자동 생성

## ESLint / Prettier 적용
### ESLint 적용
* .eslintrc.json 파일 생성
    ```
    npx eslint --init
    ```
    ```
    ? How would you like to use ESLint? (Use arrow keys)
      To check syntax only
    ❯ To check syntax and find problems // 선택
      To check syntax, find problems, and enforce code style

    ? What type of modules does your project use? (Use arrow keys)
    ❯ JavaScript modules (import/export) // 선택
      CommonJS (require/exports)
      None of these

    ? Which framework does your project use?
    ❯ React // 선택
      Vue.js
      None of these

    ? Does your project use TypeScript? (y/N) y // y 입력

    ? Where does your code run? (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ❯ ◉ Browser // 선택
      ◯ Node

    ? What format do you want your config file to be in? (Use arrow keys)
      JavaScript
      YAML
    ❯ JSON // 선택

    The config that you've selected requires the following dependencies:

    @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
    ? Would you like to install them now with npm? (Y/n) Y // Y 입력
    ```
* .eslintrc.json 파일 수정
    ```
    {
        "env": {
            "browser": true,
            "es6": true,
            "node": true
        },
        "extends": [
            "eslint:recommended",
            "plugin:@typescript-eslint/recommended",
            "plugin:prettier/recommended", // Prettier 적용
            "prettier/@typescript-eslint" // Prettier 적용
        ],
        "parser": "@typescript-eslint/parser",
        "parserOptions": {
            "ecmaFeatures": {
                "jsx": true
            },
            "ecmaVersion": 2018,
            "sourceType": "module"
        },
        "plugins": [
            "react",
            "@typescript-eslint"
        ],
        "rules": { // endOfLine Error 제거
            "prettier/prettier": [
                "error", {
                    "endOfLine": "auto"
                }
            ]
        }
    }
    ```
* setting.json 수정
    ```
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true,
    },
    ```
### Prettier 적용
* 필수 패키지 설치
    ```
    npm install prettier eslint-config-prettier eslint-plugin-prettier
    ```
* root폴더에 .prettierrc 파일 생성 및 작성
    ```
    {
        "singleQuote": true,
        "semi": true,
        "tabWidth": 4,
        "trailingComma": "all",
        "printWidth": 120,
        "arrowParens": "always"
    }
    ```
* setting.json 수정
    ```
    "[typescript]": {
        "editor.formatOnSave": true
    },
    "[typescriptreact]": {
        "editor.formatOnSave": true
    },
    ```

## MobX 적용
* 필수 패키지 설치
    ```
    npm install --save mobx mobx-react
    ```
### Decorator 없이 적용
* /modules/mobx/counterStore.tsx 생성 및 작성
    ```
    import { observable } from 'mobx';

    export interface ICounterStore {
        number: number;
        increase(): void;
        decrease(): void;
    }

    const CounterStore: ICounterStore = observable({
        number: 1,
        increase() {
            console.log('increase');
            this.number++;
        },

        decrease() {
            console.log('decrease');
            this.number--;
        },
    });

    export default CounterStore;
    ```
* /modules/mobx/useStore.tsx 생성 및 작성
    ```
    import CounterStore from './counterStore';
    import { ICounterStore } from './counterStore';

    interface IuseStore {
        CounterStore: ICounterStore;
    }

    const useStore = (): IuseStore => ({ CounterStore });

    export default useStore;
    ```
* /pages/hello.tsx 생성 및 작성
    ```
    import * as React from 'react';
    import { useObserver } from 'mobx-react';
    import useStore from '../modules/mobx/useStore';

    const Hello = (): JSX.Element => {
        const { CounterStore } = useStore();

        const onIncrease = () => {
            CounterStore.increase();
        };

        const onDecrease = () => {
            CounterStore.decrease();
        };

        return useObserver(() => (
            <>
                <div>
                    <span>CounterStore의 number값: {CounterStore.number}</span>
                </div>
                <button style={{ width: '4rem', height: '2rem' }} onClick={onIncrease}>
                    증가
                </button>
                <button style={{ width: '4rem', height: '2rem' }} onClick={onDecrease}>
                    감소
                </button>
            </>
        ));
    };

    export default Hello;
    ```

## Redux 적용
* 필수 패키지 설치
    ```
    npm install  redux react-redux redux-devtools-extension @types/react-redux
    ```
* /modules/actions.tsx 파일 생성 및 작성
    ```
    export const increase = 'increase';
    export const decrease = 'decrease';

    export interface IincreaseAction {
        type: typeof increase;
    }

    export interface IdecreaseAction {
        type: typeof decrease;
    }

    export const increaseAction = (): IincreaseAction => {
        return {
            type: increase,
        };
    };

    export const decreaseAction = (): IdecreaseAction => {
        return {
            type: decrease,
        };
    };

    export type Iactions = IincreaseAction | IdecreaseAction;
    ```
* /modules/reducer/reducerCounter.tsx 파일 생성 및 작성
    ```
    import * as actions from '../actions';

    export interface IinitCounterState {
        number: number;
    }

    const initCounterState: IinitCounterState = {
        number: 1,
    };

    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    const reducerCounter = (state = initCounterState, action: actions.Iactions) => {
        switch (action.type) {
            case actions.increase: {
                return {
                    ...state,
                    number: state.number + 1,
                };
            }

            case actions.decrease: {
                return {
                    ...state,
                    number: state.number - 1,
                };
            }

            default: {
                return {
                    ...state,
                };
            }
        }
    };

    export default reducerCounter;
    ```
* /modules/reducer/reducerIndex.tsx 파일 생성 및 작성
    ```
    import { combineReducers } from 'redux';
    import reducerCounter from './reducerCounter';
    import { IinitCounterState } from './reducerCounter';

    export interface IreducerIndex {
        reducerCounter: IinitCounterState;
    }

    const reducerIndex = combineReducers({ reducerCounter });

    export default reducerIndex;
    ```
* /modules/store.tsx 파일 생성 및 작성
    ```
    import { createStore } from 'redux';
    import { composeWithDevTools } from 'redux-devtools-extension';
    import reducerIndex from './reducer/reducerIndex';

    const store = createStore(reducerIndex, composeWithDevTools());

    export default store;
    ```
* /pages/_app.tsx 파일 수정
    ```
    import { AppProps } from 'next/app';
    import AppLayout from '../component/layouts/appLayout';
    import store from '../modules/store';
    import { Provider } from 'react-redux';

    const App = ({ Component, pageProps }: AppProps): JSX.Element => {
        return (
            <>
                <Provider store={store}>
                    <AppLayout>
                        <Component {...pageProps} />
                    </AppLayout>
                </Provider>
            </>
        );
    };

    export default App;
    ```
* /pages/hello.tsx 파일 수정
    ```
    import * as React from 'react';
    import { useObserver } from 'mobx-react';
    import useStore from '../modules/mobx/useStore';
    import { useDispatch, useSelector } from 'react-redux';
    import { IreducerIndex } from '../modules/reducer/reducerIndex';
    import { increaseAction, decreaseAction } from '../modules/actions';

    const Hello = (): JSX.Element => {
        const { CounterStore } = useStore();

        const reduxNumber: number = useSelector((state: IreducerIndex) => state.reducerCounter.number);
        const dispatch = useDispatch();

        const onMobxIncrease = () => {
            CounterStore.increase();
        };

        const onMobxDecrease = () => {
            CounterStore.decrease();
        };

        const onReduxIncrease = () => {
            dispatch(increaseAction());
        };

        const onReduxDecrease = () => {
            dispatch(decreaseAction());
        };

        return useObserver(() => (
            <>
                <div>
                    <span>Mobx의 number값: {CounterStore.number}</span>
                </div>
                <button style={{ width: '4rem', height: '2rem' }} onClick={onMobxIncrease}>
                    Mobx증가
                </button>
                <button style={{ width: '4rem', height: '2rem' }} onClick={onMobxDecrease}>
                    Mobx감소
                </button>

                <div>
                    <span>Redux의 number값: {reduxNumber}</span>
                </div>
                <button style={{ width: '4rem', height: '2rem' }} onClick={onReduxIncrease}>
                    Redux증가
                </button>
                <button style={{ width: '4rem', height: '2rem' }} onClick={onReduxDecrease}>
                    Redux감소
                </button>
            </>
        ));
    };

    export default Hello;
    ```

## ENV 적용하기
* next에서는 next.config.js파일에서 자체적으로 지원
* next dev에서는 process.env.NODE_ENV가 자동으로 development로 인식 / build파일에서는 자동으로 production으로 인식
* root폴더에 next.config.js파일 생성 및 작성
    ```
    module.exports = {
        webpack: (config, { isServer }) => {
            if (!isServer) {
                config.node = {
                    fs: 'empty',
                };
            }

            return config;
        },

        env: {
            STATE_1: process.env.NODE_ENV === 'production' ? 'PROD_MOBX' : 'DEV_MOBX',
            STATE_2: process.env.NODE_ENV === 'production' ? 'PROD_REDUX' : 'DEV_REDUX',
        },
    };
    ```
* /pages/hello.tsx 파일 수정
    ```
    ...

    return useObserver(() => (
        <>
            <div>
                {process.env.STATE_1}의 number값: {CounterStore.number}
            </div>
            <button style={{ width: '4rem', height: '2rem' }} onClick={onMobxIncrease}>
                Mobx증가
            </button>
            <button style={{ width: '4rem', height: '2rem' }} onClick={onMobxDecrease}>
                Mobx감소
            </button>

            <div>
                {process.env.STATE_2}의 number값: {reduxNumber}
            </div>
            <button style={{ width: '4rem', height: '2rem' }} onClick={onReduxIncrease}>
                Redux증가
            </button>
            <button style={{ width: '4rem', height: '2rem' }} onClick={onReduxDecrease}>
                Redux감소
            </button>
        </>
    ));

    ...
    ```

## PWA 적용하기
* 필수 패키지 설치
npm install next-pwa
* /next.config.js 수정
    ```
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const withPWA = require('next-pwa');

    module.exports = withPWA({
        ...

        pwa: {
            dest: 'public',
        },

        ...
    });
    ```
* /static/manifest.json 생성 및 내용 입력
    ```
    {
        "name": "PWA App",
        "short_name": "App",
        "icons": [
        {
            "src": "/static/icons/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/static/icons/android-chrome-384x384.png",
            "sizes": "384x384",
            "type": "image/png"
        },
        {
            "src": "/static/icons/icon-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
        ],
        "theme_color": "#FFFFFF",
        "background_color": "#FFFFFF",
        "start_url": "/",
        "display": "standalone",
        "orientation": "portrait"
    }
    ```
* /static/icons에 manifest.json의 icons에 등록된 파일 사이즈대로 집어넣기
* /pages/_document.tsx 파일 생성 및 내용 입력
    ```
    import * as React from 'react';
    import Document, { Html, Head, Main, NextScript } from 'next/document';

    class MyDocument extends Document {
        render(): JSX.Element {
            return (
                <Html>
                    <Head>
                        <meta name="application-name" content="PWA App" />
                        <meta name="apple-mobile-web-app-capable" content="yes" />
                        <meta name="apple-mobile-web-app-status-bar-style" content="default" />
                        <meta name="apple-mobile-web-app-title" content="PWA App" />
                        <meta name="description" content="Best PWA App in the world" />
                        <meta name="format-detection" content="telephone=no" />
                        <meta name="mobile-web-app-capable" content="yes" />
                        <meta name="msapplication-config" content="/static/icons/browserconfig.xml" />
                        <meta name="msapplication-TileColor" content="#2B5797" />
                        <meta name="msapplication-tap-highlight" content="no" />
                        <meta name="theme-color" content="#000000" />

                        <link rel="apple-touch-icon" sizes="180x180" href="/static/icons/apple-touch-icon.png" />
                        <link rel="icon" type="image/png" sizes="32x32" href="/static/icons/favicon-32x32.png" />
                        <link rel="icon" type="image/png" sizes="16x16" href="/static/icons/favicon-16x16.png" />
                        <link rel="manifest" href="/static/manifest.json" />
                        <link rel="mask-icon" href="/static/icons/safari-pinned-tab.svg" color="#5bbad5" />
                        <link rel="shortcut icon" href="/static/icons/favicon.ico" />
                        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />

                        <meta name="twitter:card" content="summary" />
                        <meta name="twitter:url" content="https://yourdomain.com" />
                        <meta name="twitter:title" content="PWA App" />
                        <meta name="twitter:description" content="Best PWA App in the world" />
                        <meta
                            name="twitter:image"
                            content="https://yourdomain.com/static/icons/android-chrome-192x192.png"
                        />
                        <meta name="twitter:creator" content="@DavidWShadow" />
                        <meta property="og:type" content="website" />
                        <meta property="og:title" content="PWA App" />
                        <meta property="og:description" content="Best PWA App in the world" />
                        <meta property="og:site_name" content="PWA App" />
                        <meta property="og:url" content="https://yourdomain.com" />
                        <meta property="og:image" content="https://yourdomain.com/static/icons/apple-touch-icon.png" />
                    </Head>
                    <body>
                        <Main />
                        <NextScript />
                    </body>
                </Html>
            );
        }
    }

    export default MyDocument;
    ```
* next run build / next run start 실행 후 웹페이지에서 +버튼을 이용하여 다운로드<file_sep>import java.util.LinkedList;
import java.util.Queue;

public class Programmers_블록이동하기 {
	public static void main(String[] args) {
		 System.out.println(solution(new int[][]{{0, 0, 0, 1, 1},{0, 0, 0, 1, 0},{0, 1, 0, 1, 1},{1, 1, 0, 0, 1},{0, 0, 0, 0, 0}}));
		 System.out.println(solution(new int[][]{{0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 1, 1}, {0, 0, 1, 0, 0, 0, 0}}));
		 System.out.println(solution(new int[][]{{0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 0}}));
		 System.out.println(solution(new int[][]{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 1, 1}, {0, 0, 1, 1, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 0}}));
	}

	public static int solution(int[][] board) {
        int dirj[] = {1, 0 ,-1, 0};
        int diri[] = {0 ,1 ,0 ,-1};
        int n = board.length;
        int res = -1;
        boolean visited[][][][] = new boolean[n][n][n][n];
        Queue<Node> queue = new LinkedList<Node>();
        queue.add(new Node(0, 0, 0, 1, 0));

        while(!queue.isEmpty()) {
            Node node = queue.poll();

            if(visited[node.i1][node.j1][node.i2][node.j2] || board[node.i1][node.j1] == 1 || board[node.i2][node.j2] == 1) {
                continue;
            }

            if((node.i1 == n-1 && node.j1 == n-1) || (node.i2 == n-1 && node.j2 == n-1)) {
                res = node.count;
                break;
            }

            visited[node.i1][node.j1][node.i2][node.j2] = visited[node.i2][node.j2][node.i1][node.j1] = true;

            for(int a=0; a<4; a++) {
                int nexti1 = node.i1 + diri[a];
                int nextj1 = node.j1 + dirj[a];
                int nexti2 = node.i2 + diri[a];
                int nextj2 = node.j2 + dirj[a];

                if(nexti1 >= n || nexti1 < 0 || nextj1 >= n || nextj1 < 0 || nexti2 >= n || nexti2 < 0 || nextj2 >= n || nextj2 < 0 || visited[nexti1][nextj1][nexti2][nextj2] || board[nexti1][nextj1] == 1 || board[nexti2][nextj2] == 1) {
                    continue;
                }

                queue.add(new Node(nexti1, nextj1, nexti2, nextj2, node.count+1));
            }

            if(node.i1 == node.i2) {
                if(node.j1 > node.j2) {
                    int temp = node.j1;
                    node.j1 = node.j2;
                    node.j2 = temp;
                }

                if(node.i1 + 1 < n && board[node.i1+1][node.j1] == 0 && board[node.i2+1][node.j2] == 0) {
                    queue.add(new Node(node.i1, node.j1, node.i1+1, node.j1, node.count+1));
                    queue.add(new Node(node.i2+1, node.j2, node.i2, node.j2, node.count+1));
                }

                if(node.i1 - 1 >= 0 && board[node.i1-1][node.j1] == 0 && board[node.i2-1][node.j2] == 0) {
                    queue.add(new Node(node.i1, node.j1, node.i1-1, node.j1, node.count+1));
                    queue.add(new Node(node.i2-1, node.j2, node.i2, node.j2, node.count+1));
                }
            } else if(node.j1 == node.j2) {
                if(node.i1 > node.i2) {
                    int temp = node.i1;
                    node.i1 = node.i2;
                    node.i2 = temp;
                }

                if(node.j1 + 1 < n && board[node.i1][node.j1+1] == 0 && board[node.i2][node.j2+1] == 0) {
                    queue.add(new Node(node.i1, node.j1, node.i1, node.j1+1, node.count+1));
                    queue.add(new Node(node.i2, node.j2+1, node.i2, node.j2, node.count+1));
                }

                if(node.j1 - 1 >= 0 && board[node.i1][node.j1-1] == 0 && board[node.i2][node.j2-1] == 0) {
                    queue.add(new Node(node.i1, node.j1, node.i1, node.j1-1, node.count+1));
                    queue.add(new Node(node.i2, node.j2-1, node.i2, node.j2, node.count+1));
                }
            }
        }

        return res;
    }

    private static class Node {
        int i1, j1, i2, j2, count;
        public Node(int i1, int j1, int i2, int j2, int count) {
            this.i1 = i1;
            this.j1 = j1;
            this.i2 = i2;
            this.j2 = j2;
            this.count = count;
        }
    }
}
<file_sep># CRA(Create React App)을 사용하지 않고 환경 설정
## 기본 설치
* Node.js 설치
  * https://nodejs.org/ko/ 로 접속 or 검색창에 Node.js 검색
* yarn 설치
  * VS Code 상단바에 있는 Terminal을 눌러 New Terminal or 키보드로 ctrl + shift + ` 입력
  * terminal에서 npm install -g yarn 입력
## Extension 설정
* ES7 React/Redux/GraphQL/React-Native snippets : 자주 사용하는 snippet들을 단축키로 간단히 생성
* Bracket Pair Colorizer : 여는 대괄호와 닫는 대괄호를 같은 색으로 표시
* Auto Rename Tag : 마크업 앞 혹은 뒤를 수정하면 그에 대응하는 마크업으로 자동 수정
* Prettier - Code formatter : 선호하는 포맷에 따라 커스터마이징하여 사용
* Live Server : 로컬 개발 서버를 생성
## 기본 설정
* root 폴더 생성 후 terminal에 해당 폴더까지 들어가기
* package.json 생성 : 노드 프로젝트에 대한 정보를 담음
  ```
  npm init
  ```
* 내용 입력
  ```
  package name: {패키지 명} ex) video_chat
  version: {버전} ex) (1.0.0)
  description: {세부 설명} ex) video chat visual
  entry point: {시작 지점} ex) (index.tsx)
  test command:
  git repository:
  keywords:
  author: {제작자} ex) KSC
  license: {라이센스} ex) (ISC)
  ```
* react/typescript 사용을 위한 모듈 설치
  ```
  yarn add react react-dom @types/react @types/react-dom typescript ts-node
  ```
* root폴더에 public 폴더 생성 후 index.html 생성
* index.html에 내용 입력
  ```
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>Document</title>
    </head>
    <body>
      <div id="root"></div>
    </body>
  </html>
  ```
* root폴더에 src 폴더 생성 후 App.ts 생성
* App.tsx에 내용 입력
  ```
  import React from 'react';

  const App: React.FC = () => {
      return <div>hello world!</div>
  }

  export default App;
  ```
* src폴더에 index.tsx 생성
* index.tsx에 내용 입력
  ```
  import React from 'react';
  import ReactDOM from 'react-dom';
  import App from './App';

  ReactDOM.render(<App />, document.querySelector("#root"));
  ```
* tsconfig.json 생성 : tsc를 실행하면 자동으로 찾아서 변환해주는 환경 설정
  ```
  npx typescript --init
  ```
* 주석 해제 및 변경 내역
  ```
  {
    "compilerOptions": {
      /* Visit https://aka.ms/tsconfig.json to read more about this file */

      /* Basic Options */
      "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
      "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
      "lib": ["ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020", "DOM"],                             /* Specify library files to be included in the compilation. */
      "allowJs": true,                       /* Allow javascript files to be compiled. */
      "jsx": "react",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
      "declaration": true,                   /* Generates corresponding '.d.ts' file. */
      "outDir": "./dist",                        /* Redirect output structure to the directory. */

      /* Strict Type-Checking Options */
      "strict": true,                           /* Enable all strict type-checking options. */
      "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
      "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */

      /* Module Resolution Options */
      "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
      "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
      "skipLibCheck": true,                     /* Skip type checking of declaration files. */
      "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
    },
    "exclude": ["node_modules"],
    "include": ["./src/**/*"]
  }
  ```
* 바벨에 필요한 모듈 설치 : 브라우저가 이해할 수 있는 자바스크립트의 최신 문법으로 변환 (ES5 -> ES6)
  ```
  yarn add babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
  ```
  * @babel/core : 바벨을 사용하기 위한 필수 모듈
  * @babel/preset-env : 바벨에서 스크립트 코드를 트랜스 파일링 하기 위한 플러그인들을 모아둔 모듈
  * @babel/preset-react : 바벨에서 리액트 코드를 트랜스 파일링 하기 위한 플러그인들을 모아둔 모듈
  * babel-loader : 웹팩을 돌릴 시에 바벨을 적용하기 위한 모듈
  * @babel/preset-typescript :
* root폴더에 babel.config.js 파일 생성
* babel.config.js에 내용 입력
  ```
  module.exports = function (api) {
      api.cache(true);

      const presets = [
          [
              '@babel/preset-env',
              {
                  targets: {
                      node: true,
                  },
              },
          ],
          '@babel/preset-react',
          '@babel/preset-typescript',
      ];

      const plugins = [];

      return {
          presets,
          plugins,
      };
  };
  ```
* 웹팩에 필요한 모듈 설치 : 모든 리액트 관련 파일들을 브라우저에서 이용할 수 있는 하나의 번들로 묶어 패킹하는 모듈 번들러
  ```
  yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin ts-loader
  ```
  * webpack : 웹팩 모듈 (모든 리액트 파일을 하나의 컴파일된 자바스크립트 파일로 생성)
  * webpack-cli : 웹팩 명령어를 커맨드 라인에서 실행할 때 필요한 모듈
  * html-webpack-plugin : 웹팩 실행이 완료된 번들 파일을 붙인 html 파일을 만들어 주는 모듈
  * ts-loader
* root폴더에 webpack.config.js 파일 생성
* webpack.config.js에 내용 입력
  ```
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
      mode: 'development',

      entry: './src/index',

      resolve: {
          extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },

      module: {
          rules: [
              {
                  test: /\.tsx?$/,
                  loader: ['babel-loader', 'ts-loader'], // 오른쪽에서 왼쪽 방향으로 적용되기 때문에 ts-loader가 오른쪽에 위치
                  exclude: ['/node_modules/'],
              },
          ],
      },

      output: {
          path: path.resolve(__dirname, 'dist'),
          filename: 'bundle.js',
          publicPath: '/', // 새로고침을 할 시 페이지 유지
      },

      devServer: {
          historyApiFallback: true, // 새로고침을 할 시 페이지 유지
          contentBase: path.join(__dirname, 'dist'), // 이 경로에 있는 파일이 변경될 때 번들을 다시 컴파일
          compress: true,
          port: 8888,
      },

      plugins: [
          new HtmlWebpackPlugin({
              // index.html에 output에서 만들어진 bundle.js를 적용하여, dist에 새로운 html 파일 생성
              template: `./public/index.html`,
          }),
      ],
  };
  ```
  * path : core nodejs 모듈 중 하나로 파일 경로 설정할 때 사용
  * html-webpack-plugin : index.html 파일을 dist폴더에 bundle 파일과 함께 자동으로 생성
  * mode : production or development
  * entry : 파일들을 묶기 위해 웹팩이 바라보는 파일 시작점
  * resolve : 웹팩이 자동으로 경로나 확장자를 처리할 수 있게 도와주는 옵션
  * output: bundle된 파일의 결과물을 위한 설정
  * webpack-dev-server : 변경사항이 있을 때 마다 자동으로 webpack을 수정해주는 역할, npm start할 시 자동으로 페이지 로드 수행
* package.json에 설정 추가
  ```
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --open", // 추가
    "build": "webpack --config webpack.config.js" // 추가
  },
  ```
# ESLint, Prettier 적용하기
## ESLint 설정
* TSLint를 사용하지 않고 ESLint를 사용하는 이유
  ```
  2019년에 TSLint 팀은 더이상 TSLint를 지원하지 않기로 결정했습니다.
  그 이유는 ESLint가 존재하기 때문에 동일한 의도의 목적으로 프로젝트간에 많은 중복코드가 있었기 때문입니다. TSLint 팀은 ESLint의 마이그레이션을 지원하고 ESLint의 Typescript 지원을 개선 하는데 집중한다고 밝혔습니다. 다만, TSLint를 새로운 컴파일러 버전 및 기능과 호환성을 보장하도록 지원은 할 예정이라고 합니다.
  ```
* ESLint를 위한 필수 패키지 설치
  ```
  yarn add --dev eslint eslint-config-airbnb eslint-plugin-import @typescript-eslint/eslint-plugin @typescript-eslint/parser
  ```
* root폴더에 .eslintrc 파일 생성
* .eslintrc에 내용 입력
  ```
  {
    "env": {
      "browser": true,
      "es6": true
    },
    "extends": ["airbnb", "'plugin:react/recommended", "plugin:prettier/recommended"],
    "globals": {
      "Atomics": "readonly",
      "SharedArrayBuffer": "readonly"
    },
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "settings": {
        "import/resolver": {
			"node": {
				"paths": ["src"],
				"extensions": [".d.ts", ".ts", ".tsx"]
			}
		}
    },
    "plugins": ["react", "@typescript-eslint"],
    "rules": {
      "react/jsx-filename-extension": [1, {"extensions": [".tsx"]}],
      "import/extensions": [
          "error",
          "ignorePackages", {"js": "never", "jsx": "never", "ts": "never", "tsx": "never", "json": "never"}
      ],
      "react/jsx-indent": [2, "tab"],
      "react/jsx-indent-props": [2, "tab"]
    }
  }
  ```
* 설정 창 오픈 (ctrl + ,)
* 오른쪽 상단에 3개 아이콘 중 맨 왼쪽 클릭 (파일모양)
* setting.json에 내용 입력
  ```
  "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true,
  },
  ```
  * editor.codeActionsOnSave : 저장할 때 속성값 수행
  * source.fixAll.eslint : ESLint에 정해진 대로 코드 자동 수정
## Prettier 설정
* Prettier를 위한 필수 패키지 설치
  ```
  yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier
  ```
* 설정 창 오픈 (ctrl + ,)
* 오른쪽 상단에 3개 아이콘 중 맨 왼쪽 클릭 (파일모양)
* setting.json에 내용 입력
  ```
  {
    "[typescript]": {
        "editor.formatOnSave": true
    },
    "[typescriptreact]": {
        "editor.formatOnSave": true
    },
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true,
    },
    "prettier.singleQuote": true,
    "prettier.semi": true,
    "prettier.tabWidth": 4,
    "prettier.trailingComma": "all",
    "prettier.printWidth": 120,
    "prettier.arrowParens": "always"
  }
  ```
  * singleQuote : 문자열에 싱글쿼트(') 사용
  * semi : 코드 끝에 세미클론(;) 사용
  * tabWidth : 들여쓰기 4칸
  * trailingComma : 객체나 배열 작성할 때 값 맨 뒤에거에도 쉼표(,) 사용
  * printWidth : 한 줄에 120칸 까지 작성
  * arrowParens : 화살표 함수 매개변수 하나인건 괄호 사용 안함 (const test = variable => {})
# Axios 적용하기
## 기본 설정
* axios 설치하기
  ```
  yarn add axios
  ```
## baseURL 등록하기
* src/api에 axios.js 파일 생성
* axios.js에 내용 입력
  ```
  import axios from 'axios';

  const axiosAPI = axios.create({
      baseURL: 'http://localhost:8080/videochat', // axios 사용할 때 path앞에 baseURL이 항상 추가
  });

  export default axiosAPI;
  ```
## 사용방법
* 회원가입 예제
  ```
  import axios from '../api/axios';

  axios
      .post('/user/insert', user)
      .then((res) => {
          if (res.data.data == 1) {
              console.log('회원가입 성공!');
          }
      })
      .catch((e) => {
          console.log(e);
      });
  ```
# router 적용하기
## 기본설정
* router를 위한 필수 패키지 설치
  ```
  yarn add react-router react-router-dom @types/react-router @types/react-router-dom
  ```
## 사용방법
* App.tsx 파일 수정
  ```
  import React from 'react';
  import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom';
  import SignIn from './pages/signIn';
  import SignUp from './pages/signUp';

  const App: React.FC = () => {
      return (
          <BrowserRouter>
              <Switch>
                <Route path="/user/signIn" component={SignIn} />
                <Route path="/user/signUp" component={SignUp} />
                <Redirect to="/user/signIn" />
            </Switch>
          </BrowserRouter>
      );
  };

  export default App;
  ```
* Link 사용하기 (a태그와 유사)
  ```
  <Link to="/user/signUp">signUp</Link>
  ```
* history 사용하기
  * RouteComponentProps를 제네릭으로 등록하여 사용하고 싶은 것을 prop에 history 가져오기
  * withRouter로 export default값 덮어주기 (외부에 BrowseRouter가 덮여 있어야 됨)
  * App.tsx에 적용할 거기 때문에 위의 조건 때문에 BrowseRouter를 index.tsx로 이동
  * index.tsx
    ```
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    import { BrowserRouter } from 'react-router-dom';

    ReactDOM.render(
        <BrowserRouter>
            <App />
        </BrowserRouter>,
        document.querySelector('#root'),
    );
    ```
  * App.tsx
    ```
    import React from 'react';
    import { Route, Redirect, Switch, RouteComponentProps, withRouter } from 'react-router-dom';
    import SignIn from './pages/signIn';
    import SignUp from './pages/signUp';

    const App: React.FC<RouteComponentProps> = ({ history }) => {
        const testClick = () => {
            history.push('/user/signUp');
        };
        return (
            <>
                <Switch>
                    <Route path="/user/signIn" component={SignIn} />
                    <Route path="/user/signUp" component={SignUp} />
                    <Redirect to="/user/signIn" />
                </Switch>
                <input type="button" value="test" onClick={testClick}></input>
            </>
        );
    };

    export default withRouter(App);
    ```
* match 사용하기
  * RouteComponentProps를 제네릭으로 등록하고 params로 가져와야 되는 interface를 생성하여 제네릭을 추가 등록한 뒤 prop에 match 가져오기
  * Route를 다음과 같이 등록
    ```
    <Route path="/socket/chat/:roomId" component={Chat} />
    ```
  * 예시
    ```
    import * as React from 'react';

    interface ImatchParams {
        roomId: string;
    }

    const chat: React.FC<RouteComponentProps<ImatchParams>> = ({ match }) => {
        React.useEffect(() => {
            console.log(match.params.roomId);
        }, []);

        return (
          <>
            <div>
              match Test
            </div>
          </>
        )
    }
    ```
# redux 사용 방법
## 기본 설정
* redux devtools 크롬 확장자 설치
  * https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko
* redux에 필요한 모듈 설치
  ```
  yarn add redux react-redux redux-devtools-extension @types/react-redux
  ```
* src/modules/reducer에 index.tsx 파일 생성
* index.tsx에 내용 입력
  ```
  import { combineReducers } from 'redux';

  export interface reducerState {

  }

  const rootReducer = combineReducers({

  });

  export default rootReducer;
  ```
* src/api에 interface.tsx 파일 생성
* interface.tsx에 내용 입력
  ```
  export interface IuserAuth {
      userId: string;
      userPw: string;
  }

  export interface Iuser extends IuserAuth {
      userNm: string;
      userGd: string;
      userAge: number;
      userPh: string;
      userMa: string;
      rgstTm: string;
      updtTm: string;
  }
  ```
* src/modules에 actions.tsx 파일 생성
* actions.tsx에 내용 입력
  ```
  import { Iuser } from '../api/interface';

  export const userSetUser: string = 'userSetUser';

  export interface IuserSetUserAction {
      type: typeof userSetUser;
      payload: Iuser;
  }

  export const userSetUserAction = (res: Iuser): IuserSetUserAction => {
      return {
          type: userSetUser,
          payload: res,
      };
  };

  export type reducerAction = IuserSetUserAction;
  ```
* src/modules/reducer에 user.tsx 파일 생성
* user.tsx에 내용 입력
  ```
  import * as actions from '../actions';
  import { Iuser } from '../../api/interface';

  export interface IinitUserState {
      user: Iuser;
  }

  const initUserState: IinitUserState = {
      user: {
          userId: '',
          userPw: '',
          userNm: '',
          userGd: '',
          userAge: 0,
          userPh: '',
          userMa: '',
          rgstTm: '',
          updtTm: '',
      },
  };

  const reducer = (state: IinitUserState = initUserState, action: actions.reducerAction) => {
      switch (action.type) {
          case actions.userSetUser:
              return {
                  ...state,
                  user: (action as actions.IuserSetUserAction).payload,
              };

          default:
              return {
                  ...state,
              };
      }
  };

  export default reducer;
  ```
* src/modules/reducer/index.tsx에 내용 추가
  ```
  import { combineReducers } from 'redux';
  import user from './user'; // 추가
  import { IinitUserState } from './user'; // 추기

  export interface reducerState {
      user: IinitUserState; // 추가
  }

  const rootReducer = combineReducers({
      user, // 추가
  });

  export default rootReducer;
  ```
* src/modules에 store.tsx 파일 생성
* store.tsx에 내용 입력
  ```
  import { createStore } from 'redux';
  import { composeWithDevTools } from 'redux-devtools-extension';
  import rootReducer from './reducer';

  const store = createStore(rootReducer, composeWithDevTools());

  export default store;
  ```
* src의 index.tsx파일 설정 추가
  ```
  import React from 'react';
  import ReactDOM from 'react-dom';
  import App from './App';
  import { BrowserRouter } from 'react-router-dom';
  import store from './modules/store'; // 추가
  import { Provider } from 'react-redux'; // 추기

  ReactDOM.render(
      <Provider store={store}> // 추가
          <BrowserRouter>
              <App />
          </BrowserRouter>
      </Provider>, // 추가
      document.querySelector('#root'),
  );
  ```
* src/pages폴더에 signIn.tsx파일 생성
* signIn.tsx에 내용 입력 (button을 클릭하면 redux에 데이터 저장 후 user에 데이터 연동되어 이름 표시)
  ```
  import React from 'react';
  import { useDispatch, useSelector } from 'react-redux';
  import { Iuser } from '../api/interface';
  import { userSetUserAction } from '../modules/actions';
  import { reducerState } from '../modules/reducer';

  const signIn = () => {
      const dispatch = useDispatch();
      const user: Iuser = useSelector((state: reducerState) => state.user.user);

      const testClick = () => {
          const setUser: Iuser = {
              userId: 'userId',
              userPw: '<PASSWORD>',
              userNm: 'userNm',
              userGd: 'userGd',
              userAge: 0,
              userPh: 'userPh',
              userMa: 'userMa',
              rgstTm: 'rgstTm',
              updtTm: 'updtTm',
          };

          dispatch(userSetUserAction(setUser));
      };

      return (
          <div>
              <p>SignIn 페이지입니다.</p>
              <input type="button" value="로그인" onClick={testClick}></input>
              {user.userNm}
          </div>
      );
  };

  export default signIn;
  ```
# react hook 사용 방법
* useState 사용 방법 (text에 값을 입력하면 입력된 값이 표출되는 코드)
  ```
  import React, { useState } from 'react';

  const signUp = () => {
      const [userId, setUserId] = useState<string>('');

      const onUserId = (e: React.ChangeEvent<HTMLInputElement>) => {
          setUserId(e.target.value);
      };

      return (
          <div>
              <p>SignUp 페이지입니다.</p>
              <input type="text" onChange={onUserId}></input>
              {userId}
          </div>
      );
  };

  export default signUp;
  ```
* useReducer 사용 방법 (useState와 동일한 경우)
  ```
  import React, { useState, useReducer } from 'react';

  const signUp = () => {
      interface IinitState {
          userId: string;
          userPw: string;
          userNm: string;
          userGd: string;
          userAge: number;
          userPh: string;
          userMa: string;
          rgstTm: string;
          updtTm: string;
      }

      type Iaction = { type: typeof setUserId; payload: string };

      const setUserId: string = 'setUserId';

      const initState: IinitState = {
          userId: '',
          userPw: '',
          userNm: '',
          userGd: '',
          userAge: 0,
          userPh: '',
          userMa: '',
          rgstTm: '',
          updtTm: '',
      };

      const setUserIdAction = (e: React.ChangeEvent<HTMLInputElement>) => {
          dispatchSignUp({
              type: setUserId,
              payload: e.target.value,
          });
      };

      const signUpReducer = (state: IinitState, action: Iaction) => {
          switch (action.type) {
              case setUserId: {
                  return {
                      ...state,
                      userId: action.payload,
                  };
              }

              default: {
                  return {
                      ...state,
                  };
              }
          }
      };

      const [localReducer, dispatchSignUp] = useReducer(signUpReducer, initState);

      return (
          <div>
              <p>SignUp 페이지입니다.</p>
              <input type="text" onChange={setUserIdAction}></input>
              {localReducer.userId}
          </div>
      );
  };

  export default signUp;
  ```
# redux-saga 사용하기
## 기본 설정
* 필수 패키지 설치
  ```
  yarn add redux-saga
  ```
* babel.config.js 내용 추가 // async, await 사용 위함
  ```
  module.exports = function (api) {
      api.cache(true);

      // preset-env에 targets 추가
      const presets = [
          [
              '@babel/preset-env',
              {
                  targets: {
                      node: true,
                  },
              },
          ],
          '@babel/preset-react',
      ];

      const plugins = [];

      return {
          presets,
          plugins,
      };
  };
  ```
* src/modules/saga index.tsx 파일 생성
* index.tsx에 내용 입력
  ```
  import { all } from 'redux-saga/effects';

  function* rootSaga() {
      yield all([]);
  }

  export default rootSaga;
  ```
* src/modules/saga user.tsx 파일 생성
* user.tsx에 내용 입력
  ```
  import * as actions from '../actions';
  import axios from '../../api/axios';

  const saga = [];

  export default saga;
  ```
* src/modules/store.tsx 내용 변경
  ```
  import { createStore, applyMiddleware } from 'redux'; // applyMiddleware 추가
  import { composeWithDevTools } from 'redux-devtools-extension';
  import createSagaMiddleware from 'redux-saga'; // 추가
  import rootReducer from './reducer';
  import rootSaga from './saga'; // 추가

  const sagaMiddleware = createSagaMiddleware(); // 추가
  const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware))); // DevTools 괄혼안에 applyMiddleware(sagaMiddleware) 추가

  sagaMiddleware.run(rootSaga); // 추가

  export default store;
  ```
## 예시 (로그인을 할 때 saga를 이용하여 비동기 처리하기)
* 처리되는 로직
  * saga가 반응하길 기다리고 있는(takeEvery) type을 dispatch(userSelectByUserIdAction)
  * reducer에서 해당 type에 반응(actions.userSelectByUserId)
  * saga가 해당 type을 가로채어 데이터 처리 후(userSelectByUserIdSaga) saga가 기다리지 않는 type에 put(actions.userSetUserAction(res.data.data))
  * reducer가 해당 type에 반응하여 데이터를 저장(actions.userSetUser)
* src/modules/action.tsx에 내용 추가
  ```
  import { Iuser, IuserAuth } from '../api/interface';

  // Saga
  export const userSelectByUserId: string = 'userSelectByUserId';

  // Not Saga
  export const userSetUser: string = 'userSetUser';

  // Saga
  interface IuserSelectByUserIdAction {
      type: typeof userSelectByUserId;
      payload: IuserAuth;
  }

  // Not Saga
  interface IuserSetUserAction {
      type: typeof userSetUser;
      payload: Iuser;
  }

  // Saga
  export const userSelectByUserIdAction = (res: IuserAuth): IuserSelectByUserIdAction => {
      return {
          type: userSelectByUserId,
          payload: res,
      };
  };

  // Not Saga
  export const userSetUserAction = (res: Iuser): IuserSetUserAction => {
      return {
          type: userSetUser,
          payload: res,
      };
  };

  export type reducerAction = IuserSelectByUserIdAction | IuserSetUserAction;
  ```
* src/modules/reducer/user.tsx에 내용 추가
  ```
  import * as actions from '../actions';
  import { Iuser } from '../../api/interface';

  export interface IinitUserState {
      user: Iuser;
  }

  const initUserState: IinitUserState = {
      user: {
          userId: '',
          userPw: '',
          userNm: '',
          userGd: '',
          userAge: 0,
          userPh: '',
          userMa: '',
          rgstTm: '',
          updtTm: '',
      },
  };

  const reducer = (state: IinitUserState = initUserState, action: actions.reducerAction) => {
      switch (action.type) {
          // Saga
          case actions.userSelectByUserId: {
              return {
                  ...state,
                  payload: (action as actions.IuserSelectByUserIdAction).payload,
              };
          }

          // Not Saga
          case actions.userSetUser:
              return {
                  ...state,
                  user: (action as actions.IuserSetUserAction).payload,
              };

          default:
              return {
                  ...state,
              };
      }
  };

  export default reducer;
  ```
* src/modules/saga/user.tsx에 내용 추가
  ```
  import { put, call, takeEvery } from 'redux-saga/effects';
  import * as actions from '../actions';
  import axios from '../../api/axios';

  function* userSelectByUserIdSaga(action: actions.IuserSelectByUserIdAction) {
      try {
          const res = yield call([axios, 'get'], '/user/selectByUserId', {
              params: {
                  userId: action.payload.userId,
                  userPw: action.payload.userPw,
              },
          });

          if (res.data.data === 0) {
              alert('로그인 실패');
          } else {
              yield put(actions.userSetUserAction(res.data.data));
              alert('로그인 성공');
          }
      } catch (e) {
          alert(e);
      }
  }

  const saga = [takeEvery(actions.userSelectByUserId, userSelectByUserIdSaga)];

  export default saga;
  ```
* src/pages폴더에 signIn.tsx파일 내용 추가 (사가테스트 클릭 시 axios를 통해 가져온 데이터를 화면에 표시)
  ```
  import React from 'react';
  import { useDispatch, useSelector } from 'react-redux';
  import { Iuser, IuserAuth } from '../api/interface';
  import { userSetUserAction, userSelectByUserIdAction } from '../modules/actions';
  import { reducerState } from '../modules/reducer';

  const signIn = () => {
      const dispatch = useDispatch();
      const user: Iuser = useSelector((state: reducerState) => state.user.user);

      const testClick = () => {
          const setUser: Iuser = {
              userId: 'userId',
              userPw: '<PASSWORD>',
              userNm: 'userNm',
              userGd: 'userGd',
              userAge: 0,
              userPh: 'userPh',
              userMa: 'userMa',
              rgstTm: 'rgstTm',
              updtTm: 'updtTm',
          };

          dispatch(userSetUserAction(setUser));
      };

      const sagaTest = () => {
          const testUser: IuserAuth = {
              userId: 'asd',
              userPw: 'asd',
          };
          dispatch(userSelectByUserIdAction(testUser));
      };

      return (
          <div>
              <p>SignIn 페이지입니다.</p>
              <input type="button" value="로그인" onClick={testClick}></input>
              <input type="button" value="사가테스트" onClick={sagaTest}></input>
              {user.userNm}
          </div>
      );
  };

  export default signIn;
  ```
# react 전용 UI-Framework 사용하기
## Material-UI 사용하기 (진행하던 시즌 중 1위...)
* https://material-ui.com/ 에 접속하면 관련 정보를 얻을 수 있음
* material 패키지 설치하기
  ```
  yarn add @material-ui/core
  ```
* TextField 사용법
  ```
  const setUserNmAction = (e: React.ChangeEvent<HTMLInputElement>) => {
        dispatchSignUp({
            type: setUserNm,
            payload: e.target.value,
        });
    };

  <TextField id="standard-basic" label="이름" onChange={setUserNmAction} />
  ```
* Select / MenuItem 사용법 (localReducer는 위의 useReducer 참고)
  ```
  const setUserGdAction = (
      e: React.ChangeEvent<{
          name?: string | undefined;
          value: unknown;
      }>,
  ) => {
      dispatchSignUp({
          type: setUserGd,
          payload: e.target.value as string,
      });
  };

  <Select
      labelId="demo-simple-select-label"
      id="demo-simple-select"
      value={localReducer.userGd}
      onChange={setUserGdAction}
  >
      <MenuItem value="M">남자</MenuItem>
      <MenuItem value="W">여자</MenuItem>
  </Select>
  ```
# socket-io 사용하기
## 채팅서버 생성 (새로운 폴더 생성하여 툴 새로 접속)
* package.json 생성
  ```
  npm init
  ```
* 내용 입력
  ```
  package name: {패키지 명} ex) socket
  version: {버전} ex) (1.0.0)
  description: {세부 설명} ex) socket for video chat
  entry point: {시작 지점} ex) (index.tsx)
  test command:
  git repository:
  keywords:
  author: {제작자} ex) KSC
  license: {라이센스} ex) MIT
  ```
* 필수 모듈 설치
  ```
  yarn add express socket
  ```
* root에 index.js 생성 및 입력
  ```
  const app = require('express')();
  const server = require('http').createServer(app);
  const port = 4000;
  const socketIO = require('socket.io')(server);

  server.listen(port, () => {
      console.log('채팅 서버 실행!');
  });

  socketIO.on('connection', (socket) => {
      console.log('user connected');

      socket.on('send message', (msg) => {
          console.log(msg);
          socketIO.to(msg.roomId).emit('receive message', msg);
      });

      socket.on('join room', (msg) => {
          console.log(msg);
          socket.join(msg);
      });

      socket.on('disconnect', (msg) => {
          console.log('user disconnect');
          console.log(msg);
          console.log(socket.id);
      });
  });
  ```
* 서버 실행
  ```
  node index.js
  ```
## 클라이언트와 서버 연결하기
* 필수 모듈 설치
  ```
  yarn add socket.io-client @types/socket.io-client
  ```
* 채팅리스트를 위한 reducer 설정
  * src/api/interface.tsx에 추가
    ```
    export interface Ichat {
      roomId: string;
      userId: string;
      type: string;
      contents: string;
      rgstTm: string;
    }
    ```
  * src/modules/actions.tsx에 추가
    ```
    export const socketSetChatList: string = 'socketSetChatList';
    export const socketResetChatList: string = 'socketResetChatList';

    export interface IsocketSetChatList {
        type: typeof socketSetChatList;
        payload: Ichat;
    }

    export interface IsocketResetChatList {
        type: typeof socketResetChatList;
    }

    export const socketSetChatListAction = (res: Ichat): IsocketSetChatList => {
        return {
            type: socketSetChatList,
            payload: res,
        };
    };

    export const socketResetChatListAction = (): IsocketResetChatList => {
        return {
            type: socketResetChatList,
        };
    };

    export type reducerAction =
    ...
    | IsocketSetChatList
    | IsocketResetChatList;
    ```
  * src/modules/reducer/socket.tsx 생성 및 입력
    ```
    import * as actions from '../actions';
    import { Ichat } from '../../api/interface';

    export interface IinitSocketState {
        chatList: Ichat[];
    }

    const initSocketState: IinitSocketState = {
        chatList: [],
    };

    const reducer = (state = initSocketState, action: actions.reducerAction) => {
        switch (action.type) {
            case actions.socketSetChatList: {
                let newChatList: Ichat[] = state.chatList.slice();
                newChatList.push((action as actions.IsocketSetChatList).payload);
                return {
                    ...state,
                    chatList: newChatList,
                };
            }

            case actions.socketResetChatList: {
                return {
                    ...state,
                    chatList: initSocketState.chatList,
                };
            }

            default: {
                return {
                    ...state,
                };
            }
        }
    };

    export default reducer;
    ```
  * src/modules/reducer/index.tsx 내용 수정
    ```
    import { combineReducers } from 'redux';
    import user from './user';
    import { IinitUserState } from './user';
    import { IinitSocketState } from './socket'; -- 추가
    import socket from './socket'; -- 추가

    export interface reducerState {
        user: IinitUserState;
        socket: IinitSocketState; -- 추가
    }

    const rootReducer = combineReducers({
        user,
        socket, -- 추가
    });

    export default rootReducer;
    ```
* socket을 사용할 파일 생성 및 입력
  ```
  import * as React from 'react';
  import { useDispatch, useSelector } from 'react-redux';
  import { reducerState } from '../modules/reducer/index';
  import { Iuser, Ichat } from '../api/interface';
  import { userLogoutAction, socketSetChatListAction, socketResetChatListAction } from '../modules/actions';
  import { Typography, Button, TextField } from '@material-ui/core';
  import SocketIO from 'socket.io-client';

  const main = () => {
      const dispatch = useDispatch();

      const [text, setText] = React.useState<string>('');
      const [socket, setSocket] = React.useState<SocketIOClient.Socket>();
      const [roomId, setRoomId] = React.useState<string>('');

      const reduxUser: Iuser = useSelector((state: reducerState) => state.user.user);
      const reduxChatList: Ichat[] = useSelector((state: reducerState) => state.socket.chatList);

      React.useEffect(() => {
          dispatch(socketResetChatListAction());
      }, []);

      const First = () => {
          Close();
          setRoomId('1');
          Open('1');
      };

      const Second = () => {
          Close();
          setRoomId('2');
          Open('2');
      };

      const Open = (openroom: string) => {
          if (reduxUser.userId != '' && reduxUser.userId.length > 0) {
              const connect: SocketIOClient.Socket = SocketIO.connect('http://localhost:4000');
              setSocket(connect);
              dispatch(socketResetChatListAction());

              connect.emit('join room', openroom);

              connect.emit('send message', {
                  roomId: openroom,
                  userId: reduxUser.userId,
                  type: 'alert',
                  contents: reduxUser.userId + '님이 입장하셨습니다',
                  rgstTm: '2020/08/07',
              });

              connect.on('receive message', (msg: Ichat) => {
                  console.log(msg);
                  dispatch(socketSetChatListAction(msg));
              });
          }
      };

      const Close = () => {
          const sc: SocketIOClient.Socket = socket as SocketIOClient.Socket;
          if (sc) {
              sc.disconnect();
          }
      };

      const Send = () => {
          if (text.length > 0) {
              const sc: SocketIOClient.Socket = socket as SocketIOClient.Socket;
              sc.emit('send message', {
                  roomId: roomId,
                  userId: reduxUser.userId,
                  type: 'chat',
                  contents: text,
                  rgstTm: '2020/08/07',
              });
              setText('');
          }
      };

      const EnterSend = (e: React.KeyboardEvent) => {
          if (e.key === 'Enter') {
              Send();
          }
      };

      const onText = (e: React.ChangeEvent<HTMLInputElement>) => {
          setText(e.target.value);
      };

      return (
          <>
              <div>
                  <Typography variant="h5">{reduxUser.userNm}님 안녕하세요. Main Page 입니다~</Typography> <br />
                  <Button variant="contained" color="secondary" onClick={First}>
                      1번방 참여
                  </Button>
                  <Button variant="contained" color="secondary" onClick={Second}>
                      2번방 참여
                  </Button>
                  <Button variant="contained" color="secondary" onClick={Send}>
                      Send
                  </Button>
                  <Button variant="contained" color="secondary" onClick={Close}>
                      Close
                  </Button>
                  <TextField
                      id="standard-basic"
                      label="대화"
                      value={text}
                      onChange={onText}
                      onKeyPress={EnterSend}
                      required
                  />
                  {reduxChatList.map((chat, index) => (
                      <Typography variant="h5" key={index}>
                          {chat.type === 'alert' ? chat.contents : chat.userId + ' : ' + chat.contents}
                      </Typography>
                  ))}
              </div>
          </>
      );
  };

  export default main;
  ```
# RTCPeerConnection 사용하기
## 기본개념
* ICECandidate
    ```
    ICE는 Interactive Connectivity Establishment의 약어로 두 단말이 서로 통신할 수 있는 최적의 경로를 찾도록 도와줌
    Candidate는 두 단말이 통신 가능한 모든 주소들로 Candidate를 서로 연결하여 통신을 수행함
    ```
* STUN Server
    ```
    클라이언트 자신이 사용하는 공인 IP주소를 제공해주는 역할로써 클라이언트는 자신의 공인 IP가 무엇인지 모르기 때문에
    STUN Server에 요청하여 NAT뒤에 잇는 공인IP주소를 가져와 외부와 통신할 수 있도록 도와줌
    (사설 주소와 공인주소를 바인딩)
    ```
* TURN Server
    ```
    NAT환경에 단말이 릴레이 서버를 이용하여 통신하게 해주는 것으로 STUN Server에 문제가 생겼을 때
    통화를 하는 피어들과 직접 통신하지 않고 릴레이 서버 역할을 하는 TURN 서버를 경유하여 통신하게 도와줌
    (릴레이 주소를 할당)
    ```
* 구현 동작방식 (이해한 선에서...)
    ```
    1. pc1 (local PC) / pc2 (Remote PC)를 stun / turn서버를 등록하여 생성
    2. pc1에서 getUserMedia를 통해 stream을 생성한 뒤 각각의 track들(video, audio ...)을 pc1에 추가
    3. track들을 받은 pc1은 통신할 준비가 되었다고 판단하여 onnegotiationneeded 동작
    4. pc1이 offer를 생성하여 LocalDescription에 저장한 뒤 소켓서버에 LocalDescription 전달
    5. offer를 저장한 pc1은 onicecandidate가 동작하여 모든 candidate를 생성하여 pc1에 등록
    6. pc1의 description을 받은 pc2는 RemoteDescription에 pc2의 description을 저장
    7. pc1의 description을 저장하여 track들을 확인한 pc2는 ontrack이 동작하여 videoList에 저장
    8. RemoteDescription에 pc1의 description을 저장 후 answer를 생성한 뒤 LocalDescription에 저장, 이후 소켓서버에 LocalDescription에 전달
    9. answer를 저장한 pc2은 onicecandidate가 동작하여 모든 candidate를 생성하여 pc2에 등록
    10. pc2의 description을 받은 pc1은 RemoteDescription에 pc1의 description을 저장
    11. 통신 완료
    ```
## 구현
### socket서버
* 위에 생성한 socket 서버의 index.js에 내용 추가
    ```
    ...

    socketIO.on('connection', (socket) => {

        ...

        socket.on('videoTest', (msg) => {
            socketIO.to(msg.roomId).emit('receiveTest', msg);
        });
    }
    ```
### reducer 등록
* src/modules/actions.tsx에 내용 추가
    ```
    export const socketSetVideoList: string = 'socketSetVideoList';
    export const socketResetVideoList: string = 'socketResetVideoList';

    export interface IsocketSetVideoListAction {
        type: typeof socketSetVideoList;
        payload: MediaStream;
    }

    export interface IsocketResetVideoListAction {
        type: typeof socketResetVideoList;
    }

    export const socketSetVideoListAction = (res: MediaStream): IsocketSetVideoListAction => {
        return {
            type: socketSetVideoList,
            payload: res,
        };
    };

    export const socketResetVideoListAction = (): IsocketResetVideoListAction => {
        return {
            type: socketResetVideoList,
        };
    };

    export type reducerAction =
        ...
        | IsocketSetVideoListAction
        | IsocketResetVideoListAction;
    ```
* src/modules/reducer/socket.tsx에 내용 추가
    ```
    import * as actions from '../actions';
    import { Ichat } from '../../api/interface';

    export interface IinitSocketState {
        ...
        videoList: MediaStream[];
    }

    const initSocketState: IinitSocketState = {
        ...
        videoList: [],
    };

    const reducer = (state = initSocketState, action: actions.reducerAction) => {
        switch (action.type) {
            ...

            case actions.socketSetVideoList: {
                let newVideoList: MediaStream[] = state.videoList.slice();
                newVideoList.push((action as actions.IsocketSetVideoListAction).payload);
                return {
                    ...state,
                    videoList: newVideoList,
                };
            }

            case actions.socketResetVideoList: {
                return {
                    ...state,
                    videoList: initSocketState.videoList,
                };
            }

            ...
        }
    };

    export default reducer;
    ```
### interface 작성
* src/api/interface.tsx에 내용 추가
    ```
    ...

    export interface Ivideochat {
        type: string;
        sdp?: RTCSessionDescription;
        candidate?: RTCIceCandidate;
        roomId: string;
    }
    ```
### component 작성
* src/components/socket/video.tsx 파일 생성 및 작성
    ```
    import * as React from 'react';

    interface Ivideo {
        stream: MediaStream;
    }

    const video = ({ stream }: Ivideo) => {
        const [videoData, setVideoData] = React.useState<HTMLMediaElement | undefined>(undefined);

        React.useEffect(() => {
            if (videoData) {
                (videoData as HTMLMediaElement).srcObject = stream;
            }
        }, [videoData]);

        return (
            <>
                <div>
                    <video ref={(node: HTMLVideoElement) => setVideoData(node)} width="480px" autoPlay={true}></video>
                </div>
            </>
        );
    };

    export default video;
    ```
### Page 작성
* src/pages/socket/socketMain.tsx 파일 생성 및 작성
    ```
    import * as React from 'react';
    import { useDispatch, useSelector } from 'react-redux';
    import SocketIO from 'socket.io-client';
    import { RouteComponentProps } from 'react-router';
    import Video from '../../components/socket/video';
    import { Ivideochat } from '../../api/interface';
    import { reducerState } from '../../modules/reducer';
    import { socketResetVideoListAction, socketSetVideoListAction } from '../../modules/actions';

    interface ImatchParams {
        roomId: string;
        userId: string;
    }

    const socketMain: React.FC<RouteComponentProps<ImatchParams>> = ({ match }) => {
        const dispatch = useDispatch();

        const [socket, setSocket] = React.useState<SocketIOClient.Socket | undefined>(undefined);

        const videoList: MediaStream[] = useSelector((state: reducerState) => state.socket.videoList);

        const pcConfig = {
            iceServers: [
                {
                    urls: 'stun:stun.l.google.com:19302',
                },
                { urls: 'turn:numb.viagenie.ca', credential: 'muazkh', username: '<EMAIL>' },
            ],
        };

        React.useEffect(() => {
            Close();
            dispatch(socketResetVideoListAction());
            const connect: SocketIOClient.Socket = SocketIO.connect('http://localhost:4000');
            connect.id = match.params.userId;
            setSocket(connect);

            const handleVideoOfferMsg = (msg: Ivideochat) => {
                const desc = new RTCSessionDescription(msg.sdp);
                pc2.setRemoteDescription(desc)
                    .then(() => {
                        return pc2.createAnswer();
                    })
                    .then((answer) => {
                        return pc2.setLocalDescription(answer);
                    })
                    .then(() => {
                        connect.emit('videoTest', {
                            type: 'video-answer',
                            sdp: pc2.localDescription,
                            roomId: match.params.roomId,
                        });
                    });
            };

            const handleVideoAnswerMsg = (msg: Ivideochat) => {
                const desc = new RTCSessionDescription(msg.sdp);
                pc1.setRemoteDescription(desc);
            };

            connect.on('receiveTest', (msg: Ivideochat) => {
                if (msg.type === 'video-offer') {
                    handleVideoOfferMsg(msg);
                } else if (msg.type === 'video-answer') {
                    handleVideoAnswerMsg(msg);
                }
            });

            const pc1negotiationneeded = () => {
                console.log('pc1negotiationneeded');
                pc1.createOffer()
                    .then((offer) => {
                        return pc1.setLocalDescription(offer);
                    })
                    .then(() => {
                        connect.emit('videoTest', {
                            type: 'video-offer',
                            sdp: pc1.localDescription,
                            roomId: match.params.roomId,
                        });
                    })
                    .catch((e) => {
                        console.log(e);
                    });
            };

            const pc1icecandidate = (event: RTCPeerConnectionIceEvent) => {
                if (event.candidate) {
                    const candidate = new RTCIceCandidate(event.candidate);
                    pc1.addIceCandidate(candidate).catch((e) => {
                        console.log(e);
                    });
                }
            };

            const pc2icecandidate = (event: RTCPeerConnectionIceEvent) => {
                if (event.candidate) {
                    const candidate = new RTCIceCandidate(event.candidate);
                    pc2.addIceCandidate(candidate).catch((e) => {
                        console.log(e);
                    });
                }
            };

            const pc2track = (event: RTCTrackEvent) => {
                if (event.track.kind == 'video') {
                    dispatch(socketSetVideoListAction(event.streams[0]));
                }
            };

            const pc1: RTCPeerConnection = new RTCPeerConnection(pcConfig);
            const pc2: RTCPeerConnection = new RTCPeerConnection(pcConfig);

            pc1.onicecandidate = pc1icecandidate;
            pc1.onnegotiationneeded = pc1negotiationneeded;
            pc2.onicecandidate = pc2icecandidate;
            pc2.ontrack = pc2track;

            navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((pc1stream) => {
                dispatch(socketSetVideoListAction(pc1stream));
                pc1stream.getTracks().forEach((track) => {
                    pc1.addTrack(track, pc1stream);
                });
            });
        }, []);

        const Close = () => {
            const sc: SocketIOClient.Socket = socket as SocketIOClient.Socket;
            if (sc) {
                sc.disconnect();
            }
        };

        return (
            <>
                <div>{videoList ? videoList.map((video, index) => <Video stream={video} key={index} />) : ''}</div>
            </>
        );
    };

    export default socketMain;
    ```
# StyledComponent 사용방법
## 기본설정
* 필수패키지 설치
    ```
    yarn add styled-components @types/styled-components
    ```
* StyledComponent 생성은 컴포넌트 밖에 생성해주기

# 같은 네트워크 상에서 다른 PC로 접근하기
* package.json 수정
    ```
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "webpack-dev-server --open --host 0.0.0.0", // 추가
        "build": "webpack --config webpack.config.js"
    },
    ```

# Local 이미지 사용방법
## 기본설정
* 패키지 설치
    ```
    yarn add file-loader url-loader
    ```
* webpack.config.js 수정
    ```
    module: {
        rules: [
            {
                ...
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'file-loader',
            },
        ],
    },
    ```
* /src/@types/import-png.d.ts 파일 생성 및 내용 작성
    ```
    declare module '*.png' {
        const content: string;
        export default content;
    }
    ```
* tsconfig.js 수정
    ```
    "compilerOptions": {

        ...

        "typeRoots": [
        "./src/@types"
        ],

        ...

    ```
* vscode 재접속<file_sep>import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Programmers_징검다리건너기 {
	public static void main(String[] args) {
		System.out.println(solution(new int[]{2, 4, 5, 3, 2, 1, 4, 2, 5, 1}, 3));
	}

	public static int solution(int[] stones, int k) {
        Set<Integer> set = new HashSet<Integer>();
        for(int i=0; i<stones.length; i++) {
            set.add(stones[i]);
        }

        List<Integer> list = new ArrayList<Integer>(set);
        Collections.sort(list, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });

        int min = 0;
        firstloop : for(int i=0; i<list.size(); i++) {
            min = list.get(i);
            int index = -1;
            while(index < stones.length) {
                for(int j=k; j>0; j--) {
                    if(index+j >= stones.length) {
                        continue firstloop;
                    }

                    if(stones[index+j]-min > 0) {
                        index = index+j;
                        break;
                    }

                    if(j == 1) {
                        break firstloop;
                    }
                }
            }
        }

        return min;
    }
}
<file_sep>const { body } = document;
let candidate: number[];
let array: number[] = [];

function chooseNumber() {
    candidate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    array = [];

    for (let i = 0; i < 4; i++) {
        const chosen = candidate.splice(Math.floor(Math.random() * (9 - i)), 1)[0];
        array.push(chosen);
    }
}

chooseNumber();
console.log(array);

const result = document.createElement('hi');
body.append(result);
const form = document.createElement('form');
const input = document.createElement('input');
form.append(input);
input.type = 'text';
input.maxLength = 4;
const button = document.createElement('button');
button.textContent = '입력!';
form.append(button);
body.append(form);

let wrongCount: number = 0;
form.addEventListener('submit', (e) => {
    e.preventDefault();
    const answer = input.value;
    if (answer === array.join('')) {
        result.textContent = '홈런';
        input.value = '';
        input.focus();
        chooseNumber();
        wrongCount = 0;
    } else {
        const answerArray: string[] = answer.split('');
        let strike: number = 0;
        let ball: number = 0;
        wrongCount += 1;
        if (wrongCount > 10) {
            result.textContent = `10번 넘게 틀려서 실패! 답은 ${array.join(',')} 였습니다!`;
            input.value = '';
            input.focus();
            chooseNumber();
            wrongCount = 0;
        } else {
            console.log('답이 틀리면', answerArray);
            for (let i: number = 0; i <= 3; i++) {
                if (Number(answerArray[i]) === array[i]) {
                    console.log('같은 자리?');
                    strike++;
                } else if (array.indexOf(Number(answerArray[i])) > -1) {
                    console.log('겹치는 숫자?');
                    ball++;
                }
            }
            result.textContent = `${strike}스트라이크 ${ball}볼입니다.`;
            input.value = '';
            input.focus();
        }
    }
});
<file_sep>import java.util.LinkedList;
import java.util.Queue;

public class Programmers_게임맵최단거리 {
	public static void main(String[] args) {
		System.out.println(solution(new int[][]{{1,0,1,1,1},{1,0,1,0,1},{1,0,1,1,1},{1,1,1,0,1},{0,0,0,0,1}}));
		System.out.println(solution(new int[][]{{1,0,1,1,1},{1,0,1,0,1},{1,0,1,1,1},{1,1,1,0,0},{0,0,0,0,1}}));
	}

	public static int solution(int[][] maps) {
        int n = maps.length;
        int m = maps[0].length;
        int diri[] = {0, 1, 0, -1};
        int dirj[] = {1, 0, -1, 0};
        int res = Integer.MAX_VALUE;

        Queue<Node> queue = new LinkedList<Node>();
        boolean visited[][] = new boolean[n][m];
        queue.add(new Node(0, 0, 1));

        while(!queue.isEmpty()) {
            Node node = queue.poll();
            if(node.i == n-1 && node.j == m-1) {
                res = Math.min(res, node.count);
                continue;
            }

            if(visited[node.i][node.j]) {
                continue;
            }

            visited[node.i][node.j] = true;

            for(int a=0; a<4; a++) {
                int nexti = node.i + diri[a];
                int nextj = node.j + dirj[a];
                if(nexti >= n || nexti < 0 || nextj >= m || nextj < 0 || visited[nexti][nextj] || maps[nexti][nextj] == 0) {
                    continue;
                }

                queue.add(new Node(nexti, nextj, node.count+1));
            }
        }

        if(res == Integer.MAX_VALUE) {
            res = -1;
        }

        return res;
    }

    static class Node {
        int i, j, count;
        public Node(int i, int j, int count) {
            this.i = i;
            this.j = j;
            this.count = count;
        }
    }
}
<file_sep># ESLint 설치방법
 * Extension에서 ESLint를 전역으로 설치
 * yarn을 이용하여 터미널에서 yarn add --dev eslint (권장)
 * npm --save-dev eslint

# .eslintrc 자동생성 방법
 1. Ctrl + Shift + p로 명령팔레트 오픈
 2. create ESLINT Configuration
 3. 개발 설정 입력

# react프로젝트 생성 방법
 * create-react-app (앱이름)
 * yarn create react-app (앱이름)

# redux, react-redux설치 방법
 * yarn add redux react-redux@next redux-devtools-extension

# Redux DevTools 크롬 확장자 설치
 * https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko<file_sep>
public class Programmers_도둑질 {
	public static void main(String[] args) {
		System.out.println(solution(new int[]{1,2,3,1}));
	}

	public static int solution(int[] money) {
		int res[][] = new int[money.length][2]; // 0: 첫번째 집 골랐을 때, 1: 첫번째 집 고르지 않았을 때
        res[0][0] = money[0];
        res[1][0] = Math.max(money[0], money[1]);
        res[1][1] = money[1];

        int max = Math.max(money[0], money[1]);

        for(int i=2; i<money.length-1; i++) {
            res[i][0] = Math.max(res[i-1][0], res[i-2][0] + money[i]);
            max = Math.max(max, res[i][0]);
        }

        for(int i=2; i<money.length; i++) {
            res[i][1] = Math.max(res[i-1][1], res[i-2][1] + money[i]);
            max = Math.max(max, res[i][1]);
        }

        return max;
    }
}

/*
집들을 방문하면서 i번째 집의 돈을 훔치기 위해서는 i-1번쩨는 방문하지 않고 i-2번째는 방문을 할 수 있다.
이에 대한 점화식으로 res[i] = MAX(res[i-1], res[i-2] + money[i])라는 식이 나온다.
한 가지 더 생각해야 되는 것은 원형 집으로 되어 있기 때문에 첫 번째 집과 마지막 집도 인접해 있는 것이다.
해당 상황을 구분하기 위해 첫 번째 집의 돈을 훔칠 때는 마지막 집의 돈은 훔치지 않는다.
첫 번째 집의 돈을 훔치지 않을 때는 마지막 집을 거치도록 하여 돈을 훔치게 한다.
res[money.length][0]일 경우는 첫 번째 집의 돈을 훔치고 마지막 집의 돈을 훔치지 않을 때 이고
res[money.length][1]일 경우는 첫 번째 집의 돈을 훔치지 않고 마지막 집의 돈을 훔칠 때 이다.
res[0][0] 같은 경우는 첫 번째 집의 돈을 훔칠 때 첫 번째 집까지 갔을 때의 최댓값이므로 money[0]과 동일하고
res[1][0] 같은 경우는 첫 번째 집의 돈을 훔칠 때 두 번째 집까지 갔을 때의 최댓값이므로 MAX(money[0], money[1])과 동일 하다.
res[0][1] 같은 경우는 첫 번째 집의 돈을 훔치지 않을 때 이므로 default 값인 0으로 두고
res[1][1] 같은 경우는 첫 번째 집의 돈을 훔치지 않을 때 두 번째 집까지 갔을 때의 최댓값이므로 money[1]과 동일하다.
이후로는 res[][0] 같은 경우는 마지막 집 전까지 점화식을 수행하고
res[][1] 같은 경우는 마지막 집까지 점화식을 수행하여 최댓값을 구한다.
*/<file_sep>## 테이블에 한글값 제대로 입력되게 하기
* set charset utf8;
* alter database 데이터베이스명 default character set utf8;
* alter table 테이블명 convert to character set utf8;

## 날짜 계산법
* 하루 더하기: date_add('2020-02-29', interval 1 day)
* 하루 빼기: date_add('2020-02-29', interval -1 day)
* 일 => day, 년 => year, 월 => month, 시간 => hour, 분 => minute, 초 => second

## 문자를 날짜로 변환시키기
* date_format('2020-02-09', '%Y-%m-%d') => 날짜 형태의 '2020-02-29'로 출력

## 파일에 있는 데이터를 데이터베이스에 저장시키기
* local_infile 설정값이 ON인지 확인하기

* 'set global local_infile = 1'을 통해 설정값 ON으로 만들기 (= 0 을 하면 설정값 OFF로 바뀜)
* 'show variables like "secure_file_priv"'을 통해 secure_file 위치 찾기 (만약 ProgramData파일이 C드라이브에서 보이지 않는다면 URL에 직접 입력하면 폴더에 들어갈 수 있음)

* 아래와 같이 infile할 파일들을 해당 폴더로 옮기기

* 아래와 같은 SQL구문으로 data infile 하기 (기존에 table은 컬럼의 갯수에 맞게 만들어 놓아야 함, 아닌 방법도 존재하기는 함)
    ```
    load data infile 'C:\\ProgramData\\MySQL\\MySQL Server 8.0\\Uploads\\tempaddr.txt'
    into table tempaddr
    fields terminated by '|'
    optionally enclosed by '"'
    lines terminated by '\n'
    ignore 1 lines;
    ```
* 만약 'invalid uft8 character string'와 같은 에러가 발생한다면 해당 파일을 utf8로 변경하여 다시 만들기
* txt파일 경우 다른 이름으로 저장을 누르게 되면 저장버튼 옆에 인코딩 설정이 가능

* 만약 'Row 31193 doesn't contain data for all columns'와 같은 에러가 발생한다면 txt파일 같은 경우는 맨 마지막에 엔터키를 눌러 마지막 칸을 공백으로 만들어 뒀을 때 제대로 동작 가능
* 참고 url: https://forums.mysql.com/read.php?152,674208,674208 <file_sep>import React from 'react';
import {Card, Icon, Avatar, Button} from 'antd';
import PropTypes from 'prop-types';

const PostCard = ({post}) => {
    return (
        <Card
            key={+post.createdAt}
            cover={post.img && }
            actions={[
                <Icon type="retweet" key="retweet" />,
                <Icon type="heart" key="heart" />,
                <Icon type="message" key="message" />,
                <Icon type="ellipsis" key="ellipsis" />
            ]}
            extra={<Button>팔로우</Button>}
        >
            <Card.Meta
                avatar={<Avatar>{post.User.nickname[0]}</Avatar>}
                title={post.User.nickname}
                description={post.content}
            />
        </Card>
    );
};

PostCard.propTypes = {
    post: PropTypes.shape({
        User: PropTypes.object,
        content: PropTypes.string,
        img: PropTypes.string,
        createdAt: PropTypes.object,
    }),
};

export default PostCard;<file_sep>import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

public class Programmers_오픈채팅방 {
	public static void main(String[] args) {
		System.out.println(Arrays.toString(solution(new String[]{"Enter uid1234 Muzi", "Enter uid4567 Prodo","Leave uid1234","Enter uid1234 Prodo","Change uid4567 Ryan"})));
	}

	public static String[] solution(String[] record) {
        Map<String, String> map = new HashMap<String, String>();
        Queue<Node> queue = new LinkedList<Node>();

        for(int i=0; i<record.length; i++) {
            String str[] = record[i].split(" ");
            String command = str[0];
            String id = str[1];

            if(command.equals("Leave")) {
                queue.add(new Node(command, id));
            } else {
                String nick = str[2];
                map.put(id, nick);
                if(command.equals("Enter")) {
                    queue.add(new Node(command, id));
                }
            }
        }

        String res[] = new String[queue.size()];
        for(int i=0 ;i<res.length; i++) {
            Node node = queue.poll();
            if(node.command.equals("Enter")) {
                res[i] = map.get(node.id) + "님이 들어왔습니다.";
            } else {
                res[i] = map.get(node.id) + "님이 나갔습니다.";
            }
        }

        return res;
    }

    private static class Node {
        String command, id;
        public Node(String command, String id) {
            this.command = command;
            this.id = id;
        }
    }
}
<file_sep># Network

## OSI 7 계층 (Open System Interconnection 7 Layer)
### OSI 7 계층이란 ?
* 네트워크에서 통신이 일어나는 과정을 7단계로 나눈 것을 의미
* 통신이 일어나는 과정을 단계별로 파악할 수 있기 때문에 계층을 나눔
### 계층 단계
  * 구조

  * 1계층
    * 물리 계층(Physical Layer)
  * 2계층
    * 데이터링크 계층(DataLink Layer)
  * 3계층
    * 네트워크 계층(Network Layer)
  * 4계층
    * 전송 계층(Transport Layer)
  * 5계층
    * 세션 계층(Session Layer)
  * 6계층
    * 표현 계층(Presentation Layer)
  * 7계층
    * 응용 계층(Application Layer)
<file_sep># MySQL 설치
## 기본설정
* yum update (좀 오래걸림...)
  ```
  sudo yum update (ubuntu는 yum대신 apt)
  ```
* mysql 필수파일 설치 (차례대로 수행)
  ```
  wget http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql-community-common-5.7.20-1.el7.x86_64.rpm
  wget http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql-community-libs-5.7.20-1.el7.x86_64.rpm
  wget http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql-community-client-5.7.20-1.el7.x86_64.rpm
  wget http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql-community-server-5.7.20-1.el7.x86_64.rpm
  ```
* mysql 시작
  ```
  sudo service mysqld start
  ```
* mysql 상태보기
  ```
  sudo service mysqld status
  ```
* mysql 접속
  ```
  mysql -u root -p (비밀번호가 없으니 비밀번호 입력시 Enter) (안될 경우 grep 'temporary password' /var/log/mysqld.log에서 임시비밀번호 확인)
  ```
* 설정 추가
  ```
  vi /etc/my.cnf
  ```
  ```
  [client]
  default-character-set = utf8

  [mysql]
  default-character-set=utf8

  [mysqld]
  datadir=/var/lib/mysql
  socket=/var/lib/mysql/mysql.sock
  character-set-server=utf8
  collation-server=utf8_general_ci
  init_connect=SET collation_connection = utf8_general_ci
  init_connect=SET NAMES utf8
  character-set-client-handshake = FALSE
  skip-character-set-client-handshake

  [mysqldump]
  default-character-set=utf8
  ```
* 방화벽 오픈
  ```
  firewall-cmd --zone=public --add-port=3306/tcp --permanent
  firewall-cmd --reload
  ```
* 원격접속 허용
  ```
  sudo service mysqld start
  ```
  ```
  GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; (에러 날 경우 CREATE USER 'root'@'%' IDENTIFIED BY 'root'; 수행 후 다시 실행) => centos에서 접속할 때는 비밀번호가 없고, 원격에서 접속할 때는 비밀번호 root
  ```
# NGINX 설치
## 기본설정
* centos yum 저장소에 외부저장소 추가 (yum 저장소에 nginx가 없기 떄문)
  ```
  vi /etc/yum.repos.d/nginx.repo
  ```
  ```
  [nginx]
  name=nginx repo
  baseurl=http://nginx.org/packages/centos/7/$basearch/
  gpgcheck=0
  enabled=1
  ```
* ngnix 설치
  ```
  yum -y install nginx
  ```
* nginx 시작
  ```
  sudo systemctl start nginx
  ```
* nginx 종료
  ```
  sudo systemctl stop nginx
  ```
* nginx 상태보기
  ```
  sudo systemctl status nginx
  ```
* 방화벽 오픈
  ```
  firewall-cmd --zone=public --add-port=80/tcp --permanent
  firewall-cmd --reload
  ```
* nginx.conf 수정
  ```
  vi /etc/nginx/nginx.conf
  ```
  ```
  events {
      worker_connections 1024;
  }

  http {
      include             /etc/nginx/mime.types;
      charset		utf-8;
      access_log /var/log/nginx/access.log;
      error_log /var/log/nginx/error.log;

      server {
          listen       80;
          charset utf-8;

          server_name  (내부 아이피);

          root (Front 배포 파일 root 폴더 위치) ex) /home/mynode/video_chat/frontend/typescript;
          index index.html;

          location /greet { // 테스트용
            return 200 'Hello NGINX';
          }

          location /videochat { // Back 배포 파일 reverse proxy 적용
              proxy_pass http://localhost:8080/videochat;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header Host $http_host;
          }

          location /socket.io { // Socket 배포 reverse proxy 적용
              proxy_pass https://localhost:4000;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection 'upgrade';
              proxy_set_header Host $host;
              proxy_cache_bypass $http_upgrade;
              add_header Front-End-Https on;
	        }

	        location / {
              try_files $uri $uri/ /index.html;
	        }
      }
  }
  ```
## SSL 적용
### Letsenrpyt 적용
* Fedora Project에서 제공되는 community 기반의 저장소 설치
  ```
  sudo yum install epel-release
  ```
* certbot 설치
  ```
  sudo yum install certbot
  ```
* nginx 전용 certbot 설치 (apache 전용도 있음)
  ```
  sudo yum install python3-certbot-nginx
  ```
* certbot을 이용하여 인증서 설치 (certonly가 있을 경우: 키만 가져옴, 없을 경우: 키를 가져오면서 설정까지 추가함 / 이메일 등 정보를 넣어야 됨)
  ```
  sudo certbot certonly --nginx -d {domain 명} ex) ksccmp.iptime.org => iptime의 ddns같은 경우는 사용자가 매우 많아서.. 몇번 계속 시도했더니 됨..
  ```
* 설치위치
  ```
  /etc/letsencrypt/live/{domain 명} ex) /etc/letsencrypt/live/ksccmp.iptime.org
  ```
* /etc/nginx/nginx.conf 수정
  ```
  vi /etc/nginx/nginx.conf
  ```
  ```
  events {
      worker_connections 1024;
  }

  http {
      include             /etc/nginx/mime.types;
      charset		utf-8;
      access_log /var/log/nginx/access.log;
      error_log /var/log/nginx/error.log;

      server {
          #listen       80; -- 수정
          listen 443 ssl;
          charset utf-8;

          server_name  (내부 아이피);

          root (Front 배포 파일 root 폴더 위치) ex) /home/mynode/video_chat/frontend/typescript;
          index index.html;

          ssl_certificate /etc/letsencrypt/live/ksccmp.iptime.org/fullchain.pem; -- 추가
	        ssl_certificate_key /etc/letsencrypt/live/ksccmp.iptime.org/privkey.pem; -- 추가

          location /greet { // 테스트용
            return 200 'Hello NGINX';
          }

          location /videochat { // Back 배포 파일 reverse proxy 적용
              proxy_pass http://localhost:8080/videochat;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header Host $http_host;
          }

          location /socket.io { // Socket 배포 reverse proxy 적용
              proxy_pass https://localhost:4000;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection 'upgrade';
              proxy_set_header Host $host;
              proxy_cache_bypass $http_upgrade;
              add_header Front-End-Https on;
	        }

	        location / {
              try_files $uri $uri/ /index.html;
	        }
      }
  }
  ```
### 사설 인증키 적용
* 저장위치 이동
  ```
  cd /etc/nginx/
  mkdir ssl
  cd /etc/nginx/ssl
  ```
* 개인키 생성
  ```
  openssl genrsa -out server.key 2048
  ```
* 공용키 생성
  ```
  openssl rsa -in server.key -pubout -out serverPublic.key
  ```
* CSR(인증요청서) 생성
  ```
  openssl req -new -key server.key -out server.csr
  ```
* CA 개인키 생성
  ```
  openssl genrsa -out CA.key 2048
  ```
* CA CSR 생성
  ```
  openssl req -x509 -new -nodes -key CA.key -days 3650 -out CA.pem
  ```
* CRT(인증서) 생성
  ```
  openssl x509 -req -in server.csr -CA CA.pem -CAkey CA.key -CAcreateserial -out server.crt -days 3650
  ```
* 방화벽 오픈
  ```
  firewall-cmd --zone=public --add-port=443/tcp --permanent
  firewall-cmd --reload
  ```
* nginx.conf 수정
  ```
  vi /etc/nginx/nginx.conf
  ```
  ```
  events {
      worker_connections 1024;
  }

  http {
      include             /etc/nginx/mime.types;
      charset		utf-8;
      access_log /var/log/nginx/access.log;
      error_log /var/log/nginx/error.log;

      server {
          #listen       80; -- 수정
          listen 443 ssl;
          charset utf-8;

          server_name  (내부 아이피);

          root (Front 배포 파일 root 폴더 위치) ex) /home/mynode/video_chat/frontend/typescript;
          index index.html;

          ssl_certificate /etc/nginx/ssl/server.crt; -- 추가
	        ssl_certificate_key /etc/nginx/ssl/server.key; -- 추가

          location /greet { // 테스트용
            return 200 'Hello NGINX';
          }

          location /videochat { // Back 배포 파일 reverse proxy 적용
              proxy_pass http://localhost:8080/videochat;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header Host $http_host;
          }

          location /socket.io { // Socket 배포 reverse proxy 적용
              proxy_pass https://localhost:4000;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection 'upgrade';
              proxy_set_header Host $host;
              proxy_cache_bypass $http_upgrade;
              add_header Front-End-Https on;
	        }

	        location / {
              try_files $uri $uri/ /index.html;
	        }
      }
  }
  ```
## Front 파일 배포
  * 폴더 생성
  ```
  cd /home/{계정} ex) /home/mynode
  mkdir {저장할 위치 만들기} ex) mkdir video_chat / cd video_chat / mkdir frontend / cd frontend / mkdir typescript
  ```
  * 저장위치까지 관련된 모든 폴더 chmod 755로 설정
  ```
  cd /
  chmod -R 777 home
  ```
  * 저장 폴더 chcon 적용
  ```
  cd {저장 폴더 전 위치} ex) cd /home/mynode/video_chat/frontend
  chcon -R -t httpd_sys_content_t typescript
  ```
  * 적용 확인
  ```
  cd typescript
  ls -lZd
  ```
# Tomcat 설치
## 기본설정
* openjdk 설치
  ```
  yum install java-1.8.0-openjdk
  yum install java-1.8.0-openjdk-devel
  ```
* 심볼릭 링크 설정
  ```
  cd /usr/lib/jvm
  ln -s java-1.8.0-openjdk-1.8.0.262.b10-0.el8_2.x86_64/ jdk
  ```
* 설정파일 수정
  ```
  vi /etc/profile
  ```
  ```
  JAVA_HOME=/usr/lib/jvm/jdk -- 추가
  PATH=$PATH:$JAVA_HOME/bin -- 추가
  CLASSPATH=$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar -- 추가

  export JAVA_HOME PATH CLASSPATH -- 추가

  pathmunge () {
  ...
  ```
* 적용 및 테스트
  ```
  source /etc/profile
  echo $JAVA_HOME
  echo $PATH
  echo $CLASSPATH
  ```
* tomcat 설치
  ```
  cd /usr/local
  wget http://archive.apache.org/dist/tomcat/tomcat-8/v8.5.27/bin/apache-tomcat-8.5.27.tar.gz
  tar xvfz apache-tomcat-8.5.27.tar.gz
  ```
* 심볼릭 링크 설정
  ```
  ln -s apache-tomcat-8.5.27/ tomcat
  ```
* 설정파일 수정
  ```
  vi /etc/profile
  ```
  ```
  JAVA_HOME=/usr/lib/jvm/jdk
  JRE_HOME=/usr/lib/jvm/jdk -- 추가
  CATALINA_HOME=/usr/local/tomcat -- 추가
  PATH=$PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin -- 변경
  CLASSPATH=$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar:$CATALINA_HOME/lib/jsp-api.jar:$CATALINA_HOME/lib/servlet-api.jar -- 변경

  export JAVA_HOME PATH CLASSPATH CATALINA_HOME JRE_HOME -- 변경

  pathmunge () {
  ...
  ```
* 적용 및 테스트
  ```
  source /etc/profile
  echo $JRE_HOME
  echo $CATALINA_HOME
  ```
* tomcat 실행
  ```
  /usr/local/tomcat/bin/startup.sh
  ```
* tomcat 중지
  ```
  /usr/local/tomcat/bin/shutdown.sh
  ```
* tomcat 실행확인
  ```
  netstat -an | grep 8080
  ```
* 방화벽 오픈
  ```
  firewall-cmd --zone=public --add-port=8080/tcp --permanent
  firewall-cmd --reload
  ```
* 파일권한 설정
  ```
  cd /usr/local/tomcat
  chmod -R 755 webapps
  chcon -R -t httpd_sys_content_t webapps
  ```
* 무슨설정인지는 모르겠지만 이것도 실행
  ```
  setsebool -P httpd_can_network_connect 1
  ```
* 정상동작 확인
  ```
  /usr/local/tomcat/bin/startup.sh
  ```
  ```
  localhost:8080 접속
  ```
## Back 파일 배포
* webapps로 이동
  ```
  cd /usr/local/tomcat/webapps
  ```
* 해당위치에 war파일을 FTP로 옮겨서 톰캣 재시작하면 war파일이 자동으로 풀림 (ex) videochat.war를 옮기면 재시작 시 videochat 폴더가 생김)
* Front에서 Back 접근방법
  ```
  const axiosAPI: AxiosInstance = axios.create({
      // baseURL: 'http://localhost:8080/videochat', // axios 사용할 때 path앞에 baseURL이 항상 추가
      baseURL: 'https://ksccmp.iptime.org/videochat', // 배포
  });
  ```
# Node 서버 배포
## 기본설정
* nodejs 설치
  ```
  yum -y install nodejs
  ```
* 저장위치 생성 후 FTP로 node 파일 옮기기 (보통 git을 통해 파일을 가져옴)
  ```
  cd /usr/local/
  mkdir socket
  cd socket
  ```
* 방화벽 오픈
  ```
  firewall-cmd --zone=public --add-port=4000/tcp --permanent
  firewall-cmd --reload
  ```
* 파일 권한 설정
  ```
  cd /usr/local/
  chmod -R 755 socket
  chcon -R -t httpd_sys_content_t socket
  ```
* node서버 시작
  ```
  node /usr/local/socket/index.js
  ```
* socket서버에 https 적용
  ```
  const app = require('express')();
  const fs = require('fs'); // 배포

  app.use(function (request, response, next) {
      response.setHeader('Content-Type', 'application/json');
      response.setHeader('Access-Control-Allow-Origin', '*');
      response.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
      next();
  });

  // const server = require('http').createServer(app); // 로컬
  const server = require('https').createServer(
      {
          key: fs.readFileSync('/etc/nginx/ssl/server.key'),
          cert: fs.readFileSync('/etc/nginx/ssl/server.crt'),
          ca: fs.readFileSync('/etc/nginx/ssl/CA.pem'),
      },
      app,
  ); // 배포
  const port = 4000;
  const socketIO = require('socket.io')(server);
  ```
* client에서 https적용된 socket 불러오기
  ```
  // const connect: SocketIOClient.Socket = SocketIO.connect('http://localhost:4000'); // 로컬
  const connect: SocketIOClient.Socket = SocketIO.connect('https://ksccmp.iptime.org/', { secure: true }); // 배포
  ```

# Turn Server 설치
* 설치 위치 이동
  ```
  cd /usr/local
  ```
* 필요 파일들 설치 및 압축 해제
  ```
  yum -y install libevent libevent-devel make
  yum -y install gcc gcc-c++ openssl-devel
  wget http://turnserver.open-sys.org/downloads/v4.5.1.0/turnserver-4.5.1.0.tar.gz
  tar -zxvf turnserver-4.5.1.0.tar.gz
  ```
* 뭔지 기억이 안남...
  ```
  cd turnserver-4.5.1.0
  ./configure
  make & make install
  ```
* 생성된 turnserver.conf파일 /etc 위치로 이동
  ```
  mv 이용
  ```
* turnserver.conf 파일 수정
  ```
  cd /etc
  vi turnserver.conf
  ```
  ```
  listening-port=3478
  tls-listening-port=5349
  external-ip= (외부아이피) => 네이버에 내 IP검색했을 때 나오는 IP
  listening-ip= (내부아이피)
  relay-ip=(내부아이피)
  min-port=49152
  max-port=65535
  verbose
  fingerprint
  lt-cred-mech
  user=sctest:sctest
  realm=(내부아이피)
  cert=/etc/letsencrypt/live/ksccmp.iptime.org/fullchain.pem
  pkey=/etc/letsencrypt/live/ksccmp.iptime.org/privkey.pem
  cipher-list="ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-CCM:DHE-RSA-AES256-SHA:DHE-PSK-AES256-CCM:DHE-PSK-AES256-CBC-SHA:ECDHE-PSK-AES256-CBC-SHA"
  CA-file=/etc/letsencrypt/live/ksccmp.iptime.org/cert.pem
  dh-file=/etc/nginx/ssl/dhparam.pem
  log-file=/var/tmp/turn.log
  no-tlsv1
  no-tlsv1_1
  ```
* linux에서 cipher-list 확인법
  ```
  /usr/bin/openssl ciphers -v
  ```
* dhparam.pem 파일 생성법
  ```
  cd /etc/nginx/ssl
  sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
  ```
* turnserver.service 파일 수정
  ```
  cd /etc/systemd/system
  vi turnserver.service
  ```
  ```
  Description=turnserver Service
  After=network.target

  [Service]
  Type=simple
  User=root
  ExecStart=/usr/local/turnserver-192.168.127.12/bin/turnserver -c /etc/turnserver.conf
  Restart=on-abort

  [Install]
  WantedBy=multi-user.target
  ```
* 방화벽 오픈
  ```
  firewall-cmd --zone=public --add-port=3478/tcp --permanent
  firewall-cmd --zone=public --add-port=5349/tcp --permanent
  firewall-cmd --zone=public --add-port=3478/udp --permanent
  firewall-cmd --zone=public --add-port=5349/udp --permanent
  firewall-cmd --zone=public --add-port=49152-65535/tcp --permanent
  firewall-cmd --zone=public --add-port=49152-65535/udp --permanent
  ```
* turnserver 실행
  ```
  systemctl start turnserver
  ```
* 결과적으로... 외부에서는 turnserver 접근이 불가능했지만 내부적으로는 turnserver가 정상동작하여 reverse proxy방법으로 turnserver 적용<file_sep>import React from 'react';
import AppLayout from '../components/AppLayout';
import PropTypes from 'prop-types';
import {Provider} from 'react-redux'; // Provider가 redux state를 제공
import {createStore, compose, applyMiddleware} from 'redux';
import withRedux from 'next-redux-wrapper';
import reducer from '../reducers';

// layout은 _app.js에서 코딩한다.
const NodeBird = ({Component, store}) => {
    return (
        <Provider store={store}>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.25.3/antd.css" />

            <AppLayout>
                <Component />
            </AppLayout>
        </Provider>
    );
};

NodeBird.propTypes = {
    Component: PropTypes.elementType, // node는 렌더링 되는 모든 것, 배열 숫자 등등 선택해서도 가능
    store: PropTypes.object,
};

export default withRedux((initialState, options) => {
    // 여기에 store 커스터마이징
    const middlewares = []; // 미들웨어는 액션과 스토어 사이에서 동작
    const enhancer = compose(applyMiddleware(...middlewares),
    !options.isServer && window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined' ? window.__REDUX_DEVTOOLS_EXTENSION__() : (f) => f,);

    const store = createStore(reducer, initialState, enhancer);
    return store;
})(NodeBird);

//export default NodeBird;<file_sep>
public class Programmers_멀리뛰기 {
	public static void main(String[] args) {
		System.out.println(solution(4));
		System.out.println(solution(3));
	}

	public static long solution(int n) {
        long res[] = new long[n+1];
        res[0] = 1;

        for(int i=1; i<=n; i++) {
            for(int j=1; j<=2; j++) {
                if(i - j >= 0) {
                    res[i] = (res[i] + res[i-j])%1234567;
                }
            }
        }

        return res[n];
    }
}
<file_sep>import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HackerRank_Picking_Numbers {
	public static int pickingNumbers(List<Integer> a) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int i=0; i<a.size(); i++) {
            int target = a.get(i);
            if(map.containsKey(target)) {
                map.put(target, map.get(target)+1);
            } else {
                map.put(target, 1);
            }
        }

        List<Integer> keys = new ArrayList<Integer>(map.keySet());
        Collections.sort(keys, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });

        int res = 2;
        for(int i=0; i<keys.size()-1; i++) {
            res = Math.max(res, Math.max(map.get(keys.get(i+1)), map.get(keys.get(i))));
            if(keys.get(i+1) - keys.get(i) == 1) {
                res = Math.max(res, map.get(keys.get(i+1)) + map.get(keys.get(i)));
            }
        }

        if(keys.size() == 1) {
            res = Math.max(res, map.get(keys.get(0)));
        }

        return res;
    }

	public static void main(String[] args) {
		Integer array[] = new Integer[]{4,6,5,3,3,1};
		System.out.println(pickingNumbers(Arrays.asList(array)));
		array = new Integer[]{1,2,2,3,1,2};
		System.out.println(pickingNumbers(Arrays.asList(array)));
		array = new Integer[]{3,3,3,3,3,3};
		System.out.println(pickingNumbers(Arrays.asList(array)));
	}
}
<file_sep># ECMAScript
## ECMAScript란 ?
* Ecma International이 ECMA-262 기술 규격에 따라 정의하고 있는 표준화된 스크립트 프로그래밍 언어
* 자바스크립트를 표준화하기 위해 만들어짐
## ES버전
* ES1 : 1997년 6월에 등장
* ES2 : 1998년 6월에 등장
* ES3 : 1999년 12월에 등장
* ES4 : 정치적 차이로 버려짐
* ES5 : 2009년 12월에 등장
    * 배열에 forEach, map, filter, reduce, some 등의 메소드 지원
    * object에 getter / setter 지원
    * JSON 지원
* ES6(ES2015) : 2015년 6월에 등장 / JavaScript의 대 변화가 이루어진 시점
    * let, const 키워드 추가
    * arrow 문법 지원 (간결해진 코드 + this 바인딩 안함)
    * iterator / generator 추가
    * module들의 import / export 추가
    * Promise 도입
* ES7(ES2016) : 2016년 6월에 등장
* ES8(ES2017) : 2017년 6월에 등장
    * async - await 도입
* ES.Next : 곧 등장 할 다음 버전의 ECMAScript를 의미
* ES 버전이 지속적으로 높아짐에 따라 브라우저에서 인식하지 못하여 처리하지 못하는 상황이 발생 --> 트랜스 파일러의 등장
## 트랜스 파일러
* 프로그램의 소스코드를 이전 버전 또는 최신 버전 등의 비슷한 수준의 언어로 변환하는 역할
* 대표적으로 Babel이 존재
* JavaScript에서는 ES6버전 이후는 브라우저에서 못 읽는 기능들이 있기 때문에 브라우저에서 읽을 수 있는 버전으로 Babel이 트랜스 파일을 해줌

# 모듈
## 모듈이란 ?
* 구현 세부사항을 캡슐화하고 공개 API를 노출하여 다른 코드에서 쉽게 로드하여 재 사용할 수 있도록 만든 코드조각
## 모듈이 생기게 된 배경
* ```<script src="jquery.js"></script>```와 같이 코드를 작성할 때 스크립트들을 윗 단에서 모두 불러오다보니 사용하지 않는 것들도 모두 불러와 접근할 수 있기 때문에 충돌의 위험성이 커지게 됨
* 쓸 코드만 미리 지정하고 안 쓸 코드들은 배제하기 위해 모듈이 등장하게 됨
## 모듈포맷(모듈문법)
* AMD (Asynchronous Module Definition) : 비동기 모듈 정의
    * 비동기적으로 필요한 파일을 다운로드 하는 방식으로 client(브라우저 환경)에서 외부 모듈을 가져올 때 유리한 방식
    * define으로 모듈화하고 require을 이용하여 사용
        * myModule.js
        ```
        define(['jquery', 'zerocho'], function($, Z) {
            console.log($);
            console.log(Z);
            return {
                a: $,
                b: Z,
            }
        });
        ```
        * 사용파일
        ```
        require(['myModule', 'TweenMax'], function (my, T) {
            console.log(my.a); // jquery
            console.log(my.b); // zerocho
            console.log(T); // TweenMax
            console.log(jquery); // undefined 또는 에러
        });
        ```
* CommonJS
    * 모든 모듈이 로컬에 다운로드가 된 이후에 실행하는 방식 (다운되기 전까지 페이지를 동작할 수 없음)
    * Node.js에서 채택하여 사용하고 있으며 server환경에서 외부 모듈을 가져올 때 유리한 방식
    * module.exports로 모듈화하고 require을 이용하여 사용
        * myModule.js
        ```
        const $ = require('jquery');
        const Z = require('zerocho');
        module.exports = {
            a: $,
            b: Z,
        };
        ```
        * 사용파일
        ```
        const my = require('myModule');
        const T = require('TweenMax');
        console.log(my.a, my.b);
        console.log(T);
        ```
* UMD
    * AMD와 CommonJS를 쓰는 두 그룹으로 나뉘어 호환이 안되고 있기 때문에 어떤 모듈방식으로 든 모듈을 선언할 수 있도록 하기 위해 등장
    * AMD, CommonJs, 기존의 window에 추가하는 방식(```<script></script>```로 등록)까지 커버가 가능
    * myModule.js
        ```
        (function (root, factory) {
            if (typeof define === 'function' && define.amd) { // AMD
                define(['jquery', 'zerocho'], factory);
            } else if (typeof module === 'object' && module.exports) { // CommonJS
                module.exports = factory(require('jquery'), require('zerocho'));
            } else { // window
                root.myModule = factory(root.$, root.Z);
            }
        }(this, function($, Z) {
            return {
                a: $,
                b: Z,
            };
        });
        ```
* ES6 모듈 포맷 (export / import)
## require vs import
### 공통점
* require과 import 모두 모듈을 가져올 때 사용하는 방식
* require은 기존의 CommonJS와 AMD 등의 모듈들을 불러올 때 사용하고 있었는데 ES6버전 이후로 import라는 것을 이용하여 언어 내부적으로 모듈 의존성을 지원
### 차이점
* require같은 경우는 모듈에 들어있는 모든 값을 한 번에 가져와서 선택하여 사용하는 방식
* import도 require와 같이 동작할 수 있지만 특정 기능들만 가져와서 사용할 수 있음
## 모듈로더
* 모듈포맷으로 작성된 모듈들을 해석하고 로드하는 역할
* 런타임할 때 실행 (클라이언트 사이드에서 불러옴)
* 모듈을 30개의 js파일로 나누어 작성하여 불러온다면 각각의 모듈들을 불러오기 때문에 30번을 불러와야댐 --> 부하 발생
* 부하를 줄이기 위해 빌드시간에 모듈들을 하나로 합치자는 생각이 고안되어 모듈 번들러가 등장
## 모듈 번들러
* 모듈로더를 대체하기 위해 등장
* 빌드타임에 실행되어 번들 파일(bundle.js)을 생성한 후 client에서는 번들 파일을 로드
* 대표적으로 Browserify / Webpack이 존재
## 순환참조
### 순환참조란 ?
* JavaScript 모듈은 런타임시 최초 import를 만날 때 단 한 번만 확인하고 반환을 하는데 반환된 모듈은 Global로 관리되고 있는 모듈 객체에 추가가 됨
* 이후 다른 곳에서 동일한 모듈을 import할 경우 재 평가를 하지 않고 모듈 객체에서 가져와 사용하게 됨
* 이런 상황에서 A모듈에서 B모듈을 import하고 B모듈에서도 A모듈을 import하게 되면 반환되지 않은 함수를 사용하여 에러가 발생되는 것을 순환참조라고 함
### 해결방법
* 모듈의 Dependency 순서를 명확히 지정
* 별도의 모듈을 import하는 파일을 만들어서 해당 파일 내부에서 모듈 import를 관리

# 객체지향 vs 함수형
## 객체지향 프로그래밍
### 객체지향 프로그래밍이란 ?
* 절차지향 프로그램과 같이 명시된 순서대로 처리하는 것이 아니라 "객체"라는 기본 단위로 나누어 객체들간의 상호작용을 하는 프로그래밍
### 객체지향 프로그래밍 특징
* 추상화(Abstract) : 목적과 관련이 없는 것을 제거하고 필요한 부분만 표현하기 위한 개념
* 다형성(Polymorphism) : 형태가 같은데 서로 다른 기능을 수행
* 상속성(Inherit) : 부모가 사용하는 특징들을 상속받아서 자식이 수행
* 캡슐화(Encapsulate) : 사용되는 여러 기능들을 하나의 캡슐에 담는다는 개념
## 함수형 프로그래밍
### 함수형 프로그래밍이란 ?
* 프로그래밍의 상태와 상태를 변경시키는 명령형 프로그래밍(절차지향, 객체지향)과는 달리 순수 함수를 조합하여 프로그래밍 하는 선언형 프로그래밍 방식
### 함수형 프로그래밍 특징
* 1급 객체 : 변수나 데이터를 구조안에 담음 / 파라미터 전달 가능 / return 값 사용 / 할당에 사용된 이름과 관계없이 고유한 구별이 가능 / 동적으로 프로펄티 할당
* 고차함수 : 함수에 함수를 파라미터로 전달 가능 / 함수의 return 값으로 함수를 사용 가능
* 불변성 : 한 번 정의된 데이터는 변경되지 않음, 변경이 필요한 경우 복사본을 만들어 작업 (array.map, array.reduce, array.filter 등이 생김)
* 순수함수 : 동일한 입력에는 항상 같은 값을 반환 / 함수의 실행이 프로그래밍의 실행에 영향을 미치지 않아야 함

# 렌더링
## 렌더링이란 ?
* 서버로부터 HTML 파일들을 받아 브라우저에 뿌려주는 과정
## 브라우저 렌더링 과정
* 사용자가 URI를 입력하여 브라우저 엔진에 전달
* 브라우저 엔진에서 URI에 대한 저장된 쿠키 등의 자료들을 가져와 렌더링 엔진으로 전달
* 렌더링 엔진 과정
    * HTML을 파싱하여 DOM(Document Object Model) 트리를 생성
    * CSS를 파싱하여 CSSOM(CSS Object Model) 트리를 생성
    * DOM 트리와 CSSOM 트리를 통해 렌더링 트리 생성
    * 레이아웃 단계에서 브라우저의 어느 위치에 어느 크기로 렌더링 트리를 그려주는지 계산
    * 페인트 단계에서 요소들을 실제 화면에 그려줌
* 리플로우 / 리페인트
    * 리플로우(Reflow) : 노드의 크기위 위치를 다시 계산하는 작업
    * 리페인트(RePaint) : 렌더링 트리를 레이아웃에 다시 그려주는 작업
    * 발생경우
        * 페이지 초기 렌더링
        * 윈도우 리사이징
        * 노드 추가 또는 제거
        * 요소의 위치, 크기 변경
        * 폰트 변경, 이미지 크기 변경

# 웹 프로토콜
## HTTP
### HTTP 란?
* 웹에서 클라이언트와 서버 간 서로 통신하기 위한 규약
### HTTP 통신 과정
* 클라이언트에서 Request 전송
* 서버에서 Response 전송
* 클라이언트에서 Response를 받은 후 종료
### HTTP 특징
* 비연결(Connectless) : 서버와 클라이언트 간에 한 번의 통신과정을 거치면 연결을 즉시 끊음
* 무상태(Stateless) : 이전에 서버와 클라이언트끼리 통신을 했던 정보들을 기억하지 않음
### HTTP 장점
* 많은 사람들이 사용하더라도 통신 유지는 최소한으로 하기 때문에 불특정 다수를 대상으로 하는 서비스에 적합
### HTTP 단점
* 비연결성을 가지고 있기 때문에 이전의 통신 상황에 대해 알 수 없음 (stateless)
* stateless 단점을 없애며 정보 유지를 위해 Cookie와 같은 브라우저 저장소들이 등장
## HTTP vs HTTPS
* HTTPS는 HTTP에서 보안를 위한 SSL이 추가되어 HTTP보다 보안성이 높음
## HTTP1.1 vs HTTP2.0
### HTTP1.1의 문제점
* HTTP1.1의 통신 방법은 한 번의 연결에 하나의 요청과 응답을 처리
* 처음 등장시에는 문제가 없었지만 데이터 처리량이 점점 많아지기 시작했고 다수의 리소스를 처리할 때 속도와 성능 이슈를 발생
* 특정 응답에 지연되는 HOL(Head of Line) Blocking 발생 (1.1은 사양상의 제한으로 Request의 순서와 Response의 순서가 같아야 됨)
* 양방향이 지연되어 RTT(Round Trip time) 증가
* 무거은 헤더구조를 가지고 있음
### HTTP2.0의 특징
* HTTP2.0은 한 번의 연결에 여러 요청을 처리하는 Multiplexed Streams
* 커넥션 상에서의 요청 다중화로 HOL(Head of Line) Blocking이 발생하지 않음
* 요청 리소스간 의존관계를 설정하는 Stream Prioritization
* Header 정보를 압축전송하는 Header Compression
## HTTP vs Socket
* HTTP는 Connectless라는 특징을 가지고 있지만 Socket같은 경우는 강제로 연결을 끊기 전까지 연결이 유지
* HTTP는 동시접속 가능한 클라이언트의 수보다 많이 접속이 가능하지만 Socket은 동시접속 클라이언트 수에 제한이 있음
* HTTP는 클라이언트의 요청없이 서버에서 응답하지 않으지만 Socket은 클라이언트의 요청이 없어도 서버에서 응답이 가능
* 실시간으로 정보교환이 필요한 경우에는 Socket통신이 좋지만 그렇지 않은 경우에는 HTTP 통신이 더욱 효과적임

# 비동기
## 비동기 방식이란 ?
* 웹페이지를 리로드 하지 않고 데이터를 불러오는 방식이며 서버에 요청을 한 후 서버가 멈추어 있지 않고 데이터 처리를 함
## 장점
* 웹페이지 속도 향상
* 서버가 처리되는 것을 기다리지 않고 처리 가능
* 동기 방식에서 불가능했던 다양한 UI가 가능 (제목이나 태그들을 페이지의 리로드 없이 수정 가능)
## 단점
* 히스토리 관리가 되지 않음
* 페이지 이동없는 통신으로 보안상의 문제가 있음
* 연속하는 데이터를 요청 시 서버 부하가 증가
* 동일출처 정책으로 다른 도메인과 통신이 불가능 (CORS 문제 발생)
## 비동기 기법
* AJAX(Asynchronous Javascript And XML) : JavaScript 라이브러리 중 하나로 브라우저가 가지고 있는 XMLHttpRequest 객체를 이용하여 페이지의 일부만을 위한 데이터를 로드하는 기법
* CallBack : ES6 버전 이전에 자바스크립트에서 사용하던 비동기 방식 (계속해서 콜백 내에 작업해야되며 깊이가 점점 깊어지는 콜백지옥 현상이 발생)
* Promise : ES6 버전에 출시되어 CallBack의 단점을 보완하며 간결하게 코드를 작성할 수 있도록 함
* Async / Await : ES8 버전에 출시되어 Promise를 보완
    * 동기적 코드 흐름으로 개발 가능
    * 코드가 간결해지고 가독성이 높아짐
    * error가 어디서 발생했는지 알기 쉬움
    * try/catch로 error 핸들링이 가능

# 자바스크립트 배열
## 실제 배열 vs 자바스크립트 배열
### 실제 배열
* 기존에 다른 프로그래밍 언어에서 사용하는 실제 배열들은 연속성과 인접성이라는 특징을 가지고 있음
* int array[4]로 선언한 배열이 메모리 주소가 1000부터 시작을 할 시 array[i]에 대한 주소 값은 1000 + (i*4)에 해당 됨 (ex) array[0] = 1000 / array[1] = 1004 / array[2] = 1008 / array[3] = 1012)
* 결과적으로 특정 배열 값에 접근하기 위해 메모리 주소를 찾을 때 (메모리 시작 주소 + (인덱스 x 블록 당 메모리 블록 갯수))를 계산한 뒤 바로 접근
### 자바스크립트 배열
* 자바스크립트에서 배열은 HashMap + LinkedList 구조로 되어있음
* 위와 동일하게 int array[4]를 선언한 뒤 array[i]에 대한 주소 값을 찾아갈 때 처음 인덱스값부터 시작하여 i에 도달할 때 까지 연결된 순서대로 탐색
* 이런 자바스크립트 배열의 특성은 배열의 길이가 길어질수록 속도가 현저히 느려지기 때문에 ES6버전에서 ArrayBuffer를 추가하여 연속성과 인접성의 특성을 가질 수 있게 됨
* 사용방법
    ```
    var buffer = new ArrayBuffer(8);
    var view   = new Int32Array(buffer);
    view[0] = 100;
    ```
* 최근에는 자바스크립트의 발전으로 같은 타입에 대해서는 자동으로 연속성과 인접성의 특성을 가지는 배열로 변환

# 호이스팅
## 호이스팅이란 ?
* 선언된 모든 변수(var, let, const 등) 및 함수(함수선억식)들을 코드 상단으로 끌어올리는 것 (할당내용은 끌어올리지 않음)
## 예시 (var)
* 작성한 코드
    ```
    console.log(hoisting); //undefined
    var hoisting = "success";
    console.log(hoisting); // 'success'
    ```
* 실제 동작되는 코드
    ```
    var hoisting;
    console.log(hoisting); //undefined
    hoisting = "success";
    console.log(hoisting); // 'success'
    ```
## 예시 (함수표현식)
* 예시
    ```
    a();
    var a = function() {
    console.log("a is not a function");
    };
    ```
* 실제 동작되는 코드
    ```
    var a;
    a(); // error, "a is not a function"
    a = function() {
    console.log("a is not a function");
    };
    ```
## let, const에서의 호이스팅
* 예시
    ```
    console.log(x); // throws a ReferenceError
    const x = 'hey';
    ```
    ```
    console.log(x); // throws a ReferenceError
    let x = 'hey';
    ```
* 에러발생 이유 : const와 let은 호이스팅이 되기는 하지만 실제 변수가 선언되어 있는 위치에 오기전까지 변수 호출할 수 없음 (Temporal Dead Zone의 영향)

# 클로저
## 실행 문맥(Execution Context) 이란?
* 실행 가능한 코드가 실행되기 위한 환경이라는 추상적인 용어로 전역 코드 단위와 함수(블럭) 코드 단위로 나타냄
* 예시
    ```
    // global context
    let a = "Hello";

    function func1() { // local execution context
        let a = 1;
        let b = 2;

        function func2() { // local execution context
            return;
        }
    }
    ```
### 동작방법
* 전역 실행 컨텍스트에서 프로그램이 시작되고 함수를 호출할 때마다 새로운 지역 실행 컨텍스트가 실행되며 자신들의 고유한 변수를 가지고 콜 스택으로 들어감
* 콜 스택에 들어간 지역 실행 컨텍스트는 return 또는 } 로 함수 호출이 끝마치면 콜스택에서 빠져나오고 반환된 값들은 해당 함수를 호출했던 실행 컨텍스트에게 반환되고 소멸됨
## lexical scope 란?
* 자바스크립트에서 변수를 찾는 범위로 지역 실행 컨텍스트에서 먼저 변수를 찾고 이후로 호출 컨텍스트에서 변수를 찾음
* 호출 컨텍스트에서도 찾지 못하면 전역 실행 컨텍스트까지 올라가고 전역에도 없다면 undefined를 반환 ()
## 클로저란 ?
* 내부함수가 정의될 때 외부 함수의 환경을 기억하는 원리를 지칭하는 용어
## 예시
* 클로저 예시
    ```
    let out = 'out value';

    function outFunc() {
        const inner = 'in value';

        function inFunc(isParam) {
            console.log(`out: ${out}`);
            console.log(`inner: ${inner}`);
            console.log(`inParam: ${inParam}`);
        }

        return inFunc;
    }

    const param = 'this is param';
    const outResult = outfunc();
    outResult(param); // out: out value \n inner: in value \n inParam: this is param;
    out = 'out change';
    outResult(param); // out: out change \n inner: in value \n inParam: this is param;
    ```
* 클로저-호이스팅 문제 예시
    ```
    for (var i=0; i<100; i++){
        setTimeout(function(){
            console.log(i);
        }, i*100);
    }
    // 결과 : 100 100 100 100 100 100 ...
    // 이유 : setTimeout이라는 비동기 함수가 100개가 실행이 순서되로 되지만 실제로 Timeout이 되어 console을 찍을 때는 i값이 이미 100까지 도달하여 100을 출력
    ```
* 클로저-호이스팅 예방 예시 (var을 let으로 변경해도 가능)
    ```
    for (var i=0; i<100; i++){
        function call(j) {
            setTimeout(function(){
                console.log(j);
            }, j*100);
        }
        call(i);
    }
    // 결과 : 0 1 2 3 4 5 6 ...
    // 이유 : Timeout이 될 때 바라보는 값이 변수로 들어온 j값이기 때문에 0부터 순서대로 출력
    ```

# 프로토타입
## 프로토타입이란 ?
* 일반적인 객체지향언어에서의 클래스를 대신하여 상속 등을 흉내내어 자바스크립트가 객체지향언어로 사용될 수 있도록 도와주는 역할
## 개념
* prototype 예시
    ```
    function Person() {}
    Person.prototype.eyes = 2;
    Person.prototype.nose = 1;
    var kim  = new Person();
    var park = new Person():
    console.log(kim.eyes); // => 2
    ```
* 위의 예시와 같이 Person.prototype이라는 Object가 어딘가에 존재하고 Person함수로부터 생성된 객체(kim, park)들은 어딘가에 존재하는 Object에 들어있는 값을 사용할 수 있음
* Prototype Object : 함수를 정의할 때 생성 / 일반적인 객체와 같으며 기본적은 속성으로 constructor와 __proto__를 가지고 있음
* 아래 그림과 같이 생성된 함수는 prototype이라는 속성을 통해 Prototype Object에 접근 가능
    <image src="image/prototype1.PNG" alt="prototype1" width="600px" height="400px">
* Prototype Link : __proto__속성을 통해 조상이었던 함수의 Prototype Object를 가리킴 / 만들어진 Prototype Object도 __proto__속성을 가지고 있고 해당 속성을 통해 Object의 Prototype Object에 접근 가능
* 위의 예처럼 kim.eyes라는 어떤 속성값을 찾으려고 할 때 Object의 Prototype Object까지 도달해도 값을 찾지 못하면 undefined를 표출함
    <image src="image/prototype2.PNG" alt="prototype2" width="600px" height="400px">

# 이벤트루프 / 동시성모델
## 동시성
* JavaScript는 한 번에 하나의 작업만 처리하는 싱글스레드 언어인데 수많은 작업들이 동시에 처리되는 것처럼 보여짐
* 이러한 동시성모델은 이벤트루프를 기반으로 만들어짐
## 이벤트루프
* 호출스택(Call Stack) : JavaScript에서 실행되는 함수의 현재 위치 및 상태를 기록하는 자료 구조 / 현재 어떤 함수가 동작되고 다음에 어떤 함수가 호출되는지를 제어
* 이벤트큐(Event Queue) : 런타임 시점에 처리해야 할 Task를 담아놓은 큐로 비동기 이벤트(DOM Event, Ajax, SetTimeout 등)들의 작업이 완료된 뒤 실행 될 Task들이 이벤트 큐에 쌓임
* 이벤트루프 : Call Stack 내에서 실행중인 Task들이 있는지를 확인하고 Call Stack이 비워지게 될 경우 Event Queue 내의 Task가 Call Stack으로 이동되어 실행 되는것을 반복시켜줌
    <image src="image/eventloop.PNG" alt="eventloop" width="600px" height="400px">

# Package Manager
## Package Manager 란?
* 패키지를 다루는 작업(패키지 설치, 수정, 업데이트 ,삭제)을 편리하고 안전하게 수행하기 위해 사용되는 툴
* 패키지는 라이브러리와 유사한 개념으로 라이브러리가 코드의 작성을 위해 사용되는 코드의 묶음이면 패키지는 코드의 배포를 위해 사용되는 코드의 묶음이라고 볼 수 있음
* 따라서 패키지는 경우에 따라 라이브러리를 포함할 수도 있고 실행파일 등을 포함
* 패키지가 소유하고 있는 정보 : 컴파일한 소프트웨어의 바이너리, 환경설정 관련 정보, Dependency 관련 정보
## Denpendency 란?
* 많은 패키지들은 다른 관련 패키지가 설치되어 있어야만 정상적으로 동작을 함.
* 해당 경우에 기존 패키지를 실행시키기 위해 필요한 다른 패키지들을 Dependency라고 부름
* 패키지를 설치할 때 각각의 패키지가 자신의 Dependency 정보를 가지고 있어 사용자가 특정 패키지를 사용할 때 쉽게 사용할 수 있게 해줌
    * 자신의 Dependency 정보를 가지고 있지 않을 때 Dependency Hell이 발생할 수 있음
        * 예시 : A라는 패키지를 설치하는데 B라는 Dependency가 필요함. B라는 Denpendency를 설치하는 도중 B의 Dependency로 A라는 Dependency가 필요.
## Package Manager의 역할
* 패키지의 Dependency 관리
* 패키지의 보안관리 : 신뢰 + 손상방지
* 여러 패키지를 기능에 따라 그룹으로 관리
* 패키지 압축 해제
* 소프트웨어를 저장하고 관리하는 저장소인 Software Repository로부터 패키지를 찾고, 다운로드 / 설치 / 업데이트까지 수행
## npm vs yarn
* npm은 Node.js에서 주로 사용하는 패키지매니저
* yarn은 페이스북에서 개발한 새로운 자바스크립트 패키지매니저로 더 빠르게 패키지를 설치하고 의존성 관리르 다양한 디바이스에서 할 수 있게 하기 위해 개발
* yarn 공식홈페이지에서 설명하는 다른 패키지매니저와의 차이점 (고속 / 보안 / 신뢰성 / 오프라인 모드 / 결정적 / 네트워크 성능 / 동일 패키지 / 네트워크 복구 / 플랫 모드)
* 속도면에서는 yarn이 더욱 빨랐으나 최근에는 npm의 punching back을 통해 거의 차이가 나지 않음 (그래도 yarn이 근소하게 빠름)
* 신뢰성(안정성)면에서는 npm과 yarn 모두 문제없이 잘 사용되고 있기 때문에 비교하기 힘듬
* 보안면에서는 npm은 패키지를 설치할 때 자동으로 코드와 의존성을 실행할수 있도록 허용하였는데 이부분에서 위험도가 증가 (특허 정책 없이 등록한 패키지 등)
* 하지만 yarn은 yarn.lock이나 package.json으로 부터 설치만 함, 그리고 yarn.lock은 모든 디바이스에서 같은 패키지를 설치하는 것을 보장하여 다른 버전 설치로부터 오는 버그 양을 줄임
* 현재 사용빈도는 npm이 yarn보다 더 높은 상태
## package.json
### package.json 이란?
* Package Manager로부터 설치한 패키지들에 관한 정보와 의존중인 정보를 담고 있는 파일
### dependencies vs devdependencies
* 둘다 패키지에 관련된 dependency들을 담아두고 있음
* 가장 큰 차이점은 dependencies는 운영(프로덕션)환경에서 응용 프로그램에 필요한 패키지이고 devdependencies는 로컬 개발 및 테스트에서만 필요한 패키지

# 이벤트 전달 방식
## 이벤트 버블링
### 이벤트 버블링 이란?
* 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성 (트리 구조상에서 상위 단계로 올라가는 것, div -> div -> div -> body)
## 이벤트 캡쳐
### 이벤트 캡쳐 란?
* 이벤트 버블링과 반대방향으로 이벤트를 전파하는 방식, 하위 단계로 해당 이벤트를 전달 (하위 단계를 클릭했을 때 상위 단계에서 부터 실행되어 옴)
### 구현 방법
* capture 속성값에 true를 지정
    ```
    var divs = document.querySelectorAll('div');
    divs.forEach(function(div) {
        div.addEventListener('click', logEvent, {
            capture: true // default 값은 false입니다.
        });
    });
    ```
## 이벤트 전달 방식 중지
* 이벤트 버블링이나 이벤트 캡쳐와 같이 복잡한 이벤트 전달 방식을 수행하고 싶지 않고 해당 화면 요소의 이벤트만 신경쓰고 싶을 때 이벤트 전달을 중지함
* event.stopPropagation을 사용하여 이벤트 전달 중지
    ```
    divs.forEach(function(div) {
        div.addEventListener('click', logEvent);
    });

    function logEvent(event) {
        event.stopPropagation();
        console.log(event.currentTarget.className); // three
    }
    ```
## event.stopPropagation vs event.preventDefault
* stopPropagation은 이벤트 버블링이나 이벤트 캡쳐와 같이 이벤트를 상위 또는 하위단계에 전달하는 것을 방지
* preventDefault는 a태그 클릭했을 때 클릭이벤트만 실행되고 브라우저의 행동을 막을 때 사용
## 이벤트 위임
### 이벤트 위임 이란?
* 이벤트를 등록할 때 사용해야 되는 각각의 요소들에 이벤트를 추가하는 것이 아니라 상위 요소에서 하위 요소들의 이벤트를 제어하는 방식
### 장점
* 동적인 요소들에 대한 이벤트 처리가 수월함
* 상위 요소에서 이벤트를 관리하기 때문에 하위 요소들을 자유롭게 추가 및 삭제 가능
* 이벤트핸들러 관리가 용이
* 등록 핸들러 자체가 줄어들기 때문에 메모리 누수 가능성을 줄임

# This
## 기본개념
* this는 기본적으로 window를 지칭
    ```
    this; // Window {}
    ```
* 함수안에서 this를 호출할 때에도 window를 지칭
    ```
    function a() { console.log(this); };
    a(); // Window {}
    ```
* 변수를 지정하며 함수를 생성할 때에는 변수값을 지칭
    ```
    var obj = {
        a: function() { console.log(this); },
    };
    obj.a(); // obj
    ```
* 위의 예에서 변수에 다시 집어넣을 때에는 다시 window를 지칭
    ```
    var a2 = obj.a;
    a2(); // window
    ```
* 클래스를 생성할 때 new를 통해 불러오지 않을 시에는 window를 지칭 / new를 선언할 시에는 클래스를 지칭
    ```
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.sayHi = function() {
        console.log(this.name, this.age);
    }

    Person('ZeroCho', 25);
    console.log(window.name, window.age); // ZeroCho 25

    var hero = new Person('Hero', 33); // Person {name: "Hero", age: 33}
    hero.sayHi(); // Hero 33
    ```
## Call vs Apply vs Bind
### 기본개념
* Call, Apply, Bind 모두 함수 내의 this를 변경해주는 역할을 수행
### Call vs Apply
* Call과 Apply모두 첫 번재 인자에 this로 세팅하고 싶은 객체를 넘겨 this를 해당 객체로 변경시킴
* Call같은 경우는 this 세팅 객체를 제외하고 이후 변수들을 ,로 구분
* Apply같은 경우는 this 세팅 객체를 제외하고 이후 배열로 구분
    ```
    const obj = { name : 'Tom' }
    const say = function(city) {
        console.log(`Hello, My name is ${this.name}, I live in ${city}`);
    }
    say('Seoul'); // Hello, My name is  , I live in Seoul
    say.call(obj, 'Seoul'); // Hello, My name is Tom, I live in Seoul
    say.apply(obj, ['Seoul']); // Hello, My name is Tom, I live in Seoul
    ```
### Call vs Bind
* Call과 Apply는 바로 함수를 실행시키지만 Bind는 실행하지 않고 Bound함수를 리턴시킴
* 변수 구분은 Call과 같이 ,로 구분
    ```
    const obj = { name : 'Tom' }
    const say = function(city) {
        console.log(`Hello, My name is ${this.name}, I live in ${city}`);
    }
    const boundSay = say.bind(obj);
    boundSay('Seoul'); // Hello, My name is Tom, I live in Seoul
    ```
## 메서드체이닝
### 메서드체이닝 이란?
* 메서드를 호출할 때 하나씩 호출하는 것이 아니라 컴마를 이용하여 연쇄적으로 메서드를 호출하는 것
* 반환값이 존재하지 않을 시 현재 작업중인 객체 인스터스의 this를 반환
* 예시
    ```
    var obj = {
        value: 1,
        increment: function () {
            this.value += 1;
            return this;
        },
        add: function (v) {
            this.value  += v;
            return this;
        },
        shout: function () {
            alert(this.value);
        }
    }

    // 메서드 체이닝 호출
    obj.increment().add(3).shout(); // 5
    // 위와 달리 메서드를 하나씩 호출하려면 다음과 같이 해야 한다.
    obj.increment();
    obj.add(3);
    obj.shout(); // 5
    ```
### 장점
* 코드가 간결해져 하나의 문장처럼 읽힘
### 단점
* 메서드 체이닝 라인에서 에러가 발생한다면 어느 메서드에서 발생하는지 알 수 없어 디버깅 하기가 어려움

# JavaScript 원시타입
## 종류
* 기본 데이터 타입 : number(숫자), string(문자), boolean, undefined, null
* 참조 데이터 타입 : object
## null vs undefined
### 공통점
* 두 값 모두 변수에 값이 없다는 것을 의미
### 차이점
* null같은 경우는 의도를 가지고 변수에 null값을 할당하여 변수에 값이 없다는 것을 나타냄 (null의 type은 object)
* undefined같은 경우는 변수를 선언하고 값을 할당하기 전의 형태 (undefined의 type은 undefined)

# map vs reduce
## 공통점
* 배열에서 사용하는 메서드로 반복문을 처리할 때 사용
## 차이점
* map은 forEach와 같이 전체적으로 탐색을 할 때 사용
* 예시
    ```
    const array = [1, 5, 3, 4, 2];
    const check = array.map((data) => {
        return data;
    });
    console.log(check); // [1, 5, 3, 4, 2]
    ```
* reduce는 map과 같이 배열에서 사용하는 반복문 메서드(map, filter, find 등)의 조상과 같음
* reduce로 map 만들기
    ```
    const array = [1, 5, 3, 4, 2];
    const check = array.reduce((before, cur) => {
        before.push(cur);
        return before;
    }, []);
    console.log(check); // [1, 5, 3, 4, 2]
    ```
* reduce로 filter 만들기
    ```
    const array = [1, 5, 3, 4, 2];
    const check = array.reduce((before, cur) => {
        if (cur % 2 === 0) {
            before.push(cur);
        }
        return before;
    }, []);
    console.log(check); // [4, 2]
    ```

# === vs ==
## 공통점
* 두 값의 동등성을 비교하는 연산자
## 차이점
* ===는 엄격하게 동등성을 비교하는 것으로 타입과 값이 모두 같은 지를 비교 (공식문서에서 === 사용을 권장)
* 예시
    ```
    const left = '3';
    const right = 3;
    console.log(left === right); // false
    ```
    ```
    const left = undefined;
    const right = null;
    console.log(left == right); // false
    ```
* ==는 타입을 강제형변환 한 뒤 값이 같안 지를 비교
* 예시
    ```
    const left = '3';
    const right = 3;
    console.log(left == right); // true
    ```
    ```
    const left = undefined;
    const right = null;
    console.log(left == right); // true
    ```

# inline vs inline-block vs block
## inline vs block
* inline은 대표적으로 ```<span>```태그의 성질로 text 크기만큼 점유하고 동일 라인에 붙는 성질을 소유
* inline의 가장 큰 특징은 width/height와 margin/padding의 top/bottom 속성을 사용할 수 없음
* block은 ```<p>``` ```<div>```태그 등의 성질이며 무조건 한줄을 점유하고 다음 태그는 다음 줄로 보내버림
## inline vs inline-block
* inline-block은 기본적으로 inline의 성질을 가지고 있지만 inline의 단점을 보완하기 위해 등장
* inline-block은 inline에서 사용할 수 없었던 width/height와 margin/padding의 top/bottom 속성을 사용할 수 있음

# margin vs padding
## 공통점
* 두 속성 모두 여백을 만들때 사용하는 속성
## 차이점
* margin은 바깥쪽 여백을 만들 때 사용
* padding은 안쪽 여백을 만들때 사용

# position
## position 속성 종류
* static : position 속성의 default 값으로 요소를 나열한 순서대로 배치 (top, right, bottom, left 등의 속성 사용 불가)
* relative : static이었을 때 배치되는 위치를 기준으로 상대적 위치를 지정하여 배치
* absolute : 이전에 작성된 문서의 흐름과 상관없이 여러 속성값(top, right, bottom, left)들을 이용하여 배치 (기준이 되는 위치는 가장 가까운 부모 및 조상의 position속성이 relative인 요소)
* fixed : absolute와 마찬가지로 문서의 흐름에 상관없이 위치를 결정하고 브라우저 창이 기준이 되어 스크롤을 하더라도 계속 고정되어 배치
* sticky : 기준점(ex) top:50px)을 넘지 않을 때는 relative처럼 동작하다가 넘게될 시 fixed와 같이 동작 (스크롤을 내릴 때 상단에 고정배치 시킬 때 사용)

# SASS vs SCSS vs CSS
## SASS와 SCSS 등장 배경
* CSS가 복잡한 언어는 아니지만 작업이 크고 고도화 될수록 복잡해지는 CSS 작업을 쉽게 해주며 가독성과 재사용성을 높여 유지보수를 쉽게 하기 위해 등장
* CSS보다 심플한 표기법으로 구조화하여 표현할 수 있음
* 변수 및 조건문과 반복문 그리고 믹스인(MixIn) 등을 사용할 수 있음
## SASS vs SCSS
* 문법적인 차이가 존재
* SCSS코드 (기존의 CSS구조와 유사)
    ```
    .list {
        width: 100px;
        float: left;
        li {
            color: red;
            background: url("./image.jpg");
            &:last-child {
            margin-right: -10px;
            }
        }
    }
    ```
* SASS코드 (괄호대신 들여쓰기를 사용하여 코드 작성)
    ```
    .list
        width: 100px
        float: left
        li
            color: red
            background: url("./image.jpg")
            &:last-child
                margin-right: -10px
    ```
* SASS보다 SCSS가 더 이후에 등장하였고 SCSS가 더 넓은 범용성과 CSS의 호환성 등의 장점으로 SCSS 사용을 권장 / 공식문법도 SCSS 기준으로 나와있음
## SCSS vs StyledComponent
* StyledComponent는 기본적인 CSS파일을 작성하지 않고 스타일을 적용시켜 컴포넌트화 시켜서 사용
* SCSS와 뭐가 더 좋다고 판단할 수는 없지만 개인적으로 StyledComponent가 컴포넌트 형태로 적용하기 때문에 더 직관적이고 CSS파일을 파일별로 관리하지 않아도 되어서 편리함

# 스켈레톤UI
## 스켈레톤UI 란?
* 서버에서 데이터를 불러오는 동안 사용하던 로딩 애니메이션 중에서 가장 대표적인 원형 모양 같은 애니메이션 대신에 그려지는 요소들과 똑같은 위치에 물결치는 애니메이션을 노출하는 것
## 장점
* 기존의 로딩 애니메이션들은 사용자에게 로딩이 완료되기 전까지 아무것도 할 수 없다라는 지루함을 전달했지만 스켈레톤UI는 이 부분에서 사용자 경험을 높임
## 단점
* 스켈레톤UI도 같이 설계해야 되기 떄문에 설계과정이 복잡해짐

# GET vs POST
## GET
* 주로 데이터를 읽거나 검색할 때 자주 사용되는 메소드
* GET 요청이 성공적으로 이루어지면 XML이나 JSON과 함께 200 (OK) HTTP 응답 코드를 리턴 / 에러 발생시에는 404(Not Found)나 400(Bad Request)가 주로 발생
* HTTP명세에 의하면 GET 요청은 idempotent하고 같은 요청을 여러번 하더라도 변함없이 항상 같은 응답을 받아 안전하기 때문에 데이터 변경 등의 안전하지 않은 연산에 사용하면 안됨
* GET은 서버에 데이터를 전송할 때 URL뒤 파라미터에 값을 붙여서 전송 (보안에 취약)
## POST
* 주로 새로운 리소스를 생성할 때 사용되는 메소드
* POST 요청이 성공적으로 이루어지면 201 (Created) HTTP응답 코드를 리턴
* HTTP명세에 의하면 POST 요청은 idempotent하지 않고 안전하지도 않기 때문에 같은 요청을 보낼 시 같은 정보를 담은 다른 resource를 반활항 가능성이 큼
* POST는 서버에 데이터를 전송할 때 request body에 데이터를 담아 전송 (보안에 안전)

# 브라우저 저장소
## 브라우저 저장소란 ?
* LocalStorage, SessionStorage, Cookie가 존재하며 클라이언트에 데이터를 저장하기 위한 데이터 저장소 역할을 수행
* 모두 key-value 형태로 값을 저장하여 데이터를 관리
## LocalStorage
* LocalStorage는 window.localStorage에 위치하며 데이터를 직접 지우기 전까지 계속 저장되어 있기 때문에 자동로그인 등에 사용
* localStorage.setItem('key', 'value') / localStroage.getItem('key') / localStroage.'key' = 'value' / localStorage.removeItem('key') 등과 같은 방법으로 사용
## SessionSotrage
* SessionStorage도 LocalStorage와 마찬가지로 window.SessionStorage에 위치하며 LocalStorage와 달리 윈도우나 브라우저를 닫을 경우 제거가 됨
* 사용법은 LocalStorage와 동일
## Cookie
* Cookie는 Storage들과 달리 만료기한이 있는 저장소
* 서버와 통신할 때 자동으로 Cookie값들을 서버에 같이 보냄

# 라이브러리 vs 프레임워크 vs API
## 라이브러리
### 라이브러리 란?
* 응용프로그램 개발을 위해 필요한 기능을 모아 놓은 소프트웨어
* 보통 도구라고 표현되며 어떠한 프로그램을 만들기 위해 라이브러리라는 도구들을 사용한다는 개념
## 프레임워크
### 프레임워크 란?
* 응용프로그램이나 소프트웨어 솔루션 개발을 수월하게 하기 위해 제공되는 소프트웨어 환경
* 보통 뼈대를 가지고 있는 반제품이라고 표현되며 뼈대를 유지하며 다양한 형태의 완제품을 만들어 내는 개념
## API
### API 란?
* 데이터와 기능의 집합을 제공하여 프로그램간의 서로 정보를 교환 가능하도록 하는 것
### REST 란?
* HTTP 통신에서 Resource(자원)를 명시하고 어떤 자원에 대한 CRUD 요청을 Method(GET, POST, DELETE, PUT)로 표현하여 요청하는 것
### REST API 란?
* REST기반으로 API를 제공해 주는 것 (HTTP 통신에서 자원을 명시하고 Method 요청을 통해 프로그램간의 정보 교환이 이루어지도록 하는 것)
### REST 특징
* 클라이언트 - 서버 구조 : 자원을 가지고 있는 쪽이 서버, 자원을 요청하는 쪽이 클라이언트로 각각의 역할이 확실하게 구분되어 의존성을 줄임
* 무상태성(Stateless) : 서버는 각각의 요청이 별개인 것으로 인식하여 이전의 요청에 대한 정보를 기억하지 않고 단순히 들어온 요청에 대해서만 처리를 함
* 캐시처리 가능 : HTTP라는 기존의 웹 표준을 사용하기 때문에 웹의 기존 인프라를 그대로 활용하여 캐시 기능 적용 가능
* 자체표현 : JSON형태의 메세지를 전달하기 때문에 메세지 자체만으로 어떤 내용을 전달하는지 쉽게 파악 가능
* 계층구조 : 서버와 클라이언트가 분리되어 사용되기 때문에 중간에 보안 / 로드밸런싱 / 암호화 등의 계층을 추가하여 다중 계층구조 설계 가능
* 일관된 인터페이스 : 어떤 플랫폼을 사용하는지에 무관하며 종속받지 않는 상태, HTTP표준에만 따르면 어떤 플랫폼에서도 요청이 가능
### CORS 란?
* 도메인 및 포트가 서로 다른 서버로 클라이언트가 요청했을 때 브라우저가 보안상의 이유로 API 차단하는 문제
* 서버단에서 Origin에서의 요청을 모두 허용하거나 특정 Origin만 허용함으로 써 문제 해결 가능

# 프로세스 vs 스레드
## 프로세스
### 프로세스 란?
* 실행중인 프로그램을 의미하며 디스크로부터 메모리에 적재되어 CPU의 할당을 받을 수 있는 것을 의미
### PCB (Process Control Block) 이란?
* 특정 프로세스에 대한 중요 정보를 가지고 있는 자료구조
* 프로세스 생성과 동시에 PCB가 생성되며 CPU의 할당과 관련된 정보들도 모두 PCB에 저장
* 프로세스 식별자(PID), 프로세스 상태(new, ready, running, waiting, terminated) 상태, 프로그램이 다음에 실행해야 할 명령어 주소인 프로그램 카운터(PC), CPU 스케줄링 정보 등을 모두 저장
## 스레드
### 스레드 란?
* 프로세스의 실행 단위를 의미하며 프로세스 내의 공간 또는 자원을 공유
### 멀티스레드 란?
* 하나의 프로세스를 다수의 스레드로 구분하여 자원을 공유하고 중복과 생성을 최소화하여 수행능력을 향상시키는 것
* 각각의 스레드는 독립적으로 작업을 수행하며 스택과 PC레지스터를 가지고 있음
    * 스택 : 함수 호출 시 전달되는 인자, 되돌아갈 주소값, 함수 내에서 선언하는 변수 등을 저장하는 메모리 공간 / 독립적인 스택을 가지고 있어 독립적인 함수 호출이 가능하며 즉 독립적인 실행 흐름을 가짐
    * PC레지스터 : 스레드가 CPU를 할당 받았다가 스케줄링에 의해 반환했을 때 스레드가 어디까지 수행되었는지를 기록하는 곳
### 멀티스레드 장점
* 메모리 공간과 시스템 자원의 소모가 줄어듬
* 스레드간의 통신을 할 때 동적으로 할당되는 힙이나 전역변수의 공간을 활용하기 때문에 별도의 자원을 필요로 하지 않음
* context switch를 할 때 캐시메모리를 지울 필요가 없어 빠름
    * context switch : CPU가 사용중인 프로세스의 상태를 보관하고 새로운 프로세스의 상태를 적재하는 작업
### 멀티스레드 단점
* 자원을 공유하기 때문에 한 자원에 동시에 접근하는 일이 없도록 하기 위해 동기화 작업이 필요
* 동기화 작업으로 작업 처리 순서와 공유하는 자원에 대한 접근을 통제하지만 이는 병목현상이 발생할 수 있음

# 반응형웹 vs 적응협웹
## 반응형웹
### 반응형웹 이란?
* 하나의 템플릿으로 PC, 스마트폰 등의 다양한 디스플레이에 알맞게 화면 크기가 자동으로 변하는 웹
### 반응형웹 특징
* 미디어쿼리를 사용해 디바이스의 해상도를 감지한 후 화면 크기에 알맞게 UI를 제공
* 관련된 모든 콘텐츠를 다운로드 하지만 이 중 일부 콘텐츠만 사용
* 하나의 템플릿을 가지고 디바이스에 상관없이 적용
* 모든 리소스를 다운로드하기 떄문에 로드속도가 느린편
* 개발할 때 기존 사이트를 전면 재개발하며 모든 기기에 관련하여 면밀하게 검토하고 고려해야 됨
### 장점
* 디바이스에 상관없이 인터넷만 연결되어 있다면 웹사이트에 접속하여 쉽게 사용 가능
* 업데이트비용이 적게 들며 하나의 템플릿만 관리하면 되기 때문에 관리가 쉬움
### 단점
* 모든 디바이스에 충족할 수 있는 UI/UX를 제공하기가 어려움
## 적응형웹
### 적응형웹 이란?
* 웹에 접근한 디바이스를 체크하여 디바이스에 적합한 디스플레이에 맞는 템플릿을 제공해주는 웹
* 대표적으로 네이버나 다음 같은 포털 사이트가 있음
### 적응형웹 특징
* 서버 또는 브라우저에서 디바이스를 감지하여 디바이스에 적합한 UI를 제공
* 감지된 디바이스에 필요한 리소스만 다운로드하여 사용
* 준비된 여러개의 템플릿 중 디바이스에 적합한 템플릿만 사용
* 디바이스에 필요한 리소스만 다운로드하기 때문에 로드속도가 빠른편
* 개발할 때 기존 사이트는 유지시키고 새로운 템플릿을 만들지만 각각의 디바이스마다 콘텐츠 및 리소스 제작이 필요하여 기획/디자인 단계에서 많은 시간이 필요
### 장점
* 디바이스별로 템플릿을 제작하기 때문에 높은 만족도의 UI/UX를 제공할 수 있음
### 단점
* 비용이 많이 들고 디바이스별 템플릿을 각각 관리해야 되기 때문에 관리가 어려움

# SPA (Single Page Application)
## SPA 란?
* 단일 페이지로 구성된 웹 어플리케이션을 의미하며 화면 이동시에 서버로부터 HTML을 전달받지 않고 필요한 데이터만 JSON으로 전달 받아 동적으로 렌더링하는 기술
## 장점
* 화면이 이동할 때 화면마다 화면 전체를 렌더링 할 필요가 없기 때문에 이동속도가 빠름
* 화면에 필요한 데이터만 받아 오기 때문에 서버 요청이 적어 처리과정이 효율적임
* 빠른 반응성, 화면 전환 애니메이션 등에 의해 사용자 친화적이서 UX를 높여줌
* 컴포넌트 단위로 나누어 개발하기 때문에 관리가 용이
## 단점
* 처음 화면을 로딩할 때 모든 화면을 로드하기 때문에 속도가 느림
    * Lazy Loading으로 문제 해결
* 검색엔진 최적화(SEO)가 어려움
    * Server Side Rendering(SSR)로 문제 해결
## Lazy Loading
### Lazy Loading 이란?
* 화면을 로드할 때 중요한 데이터만 로드하고 이미지, 동영상 또는 일부 데이터와 같은 중요하지 않은 데이터는 로드를 연기하는 기법
* 주로 이미지를 로드할 때 많이 사용
### 사용방법
* 스크롤할 때 적용시키고자 하는 데이터의 위치가 화면에 들어왔을 때를 script로 계산하여 표현되도록 작성
* 최근 더 좋은 방법으로는 IntersectionOberserver이라는 API를 사용하여 Lazy Loading을 구현
## SEO (Search Engine Optimization)
### SEO 란?
* 검색엔진최적화 라는 의미로 웹 사이트가 구글과 같은 검색엔진의 검색결과에서 우리의 서비스를 상위로 나올수 있도록 도와주는 기술
* 검색엔진마다 검색봇이 데이터를 수집해가는 방식이 다르기 때문에 어떤 검색엔진에 더 중요하게 노출할 것인지를 생각하여 SEO를 최적화 하는 것이 중요
### SEO로 상위 랭크되는 방법
* 문법에 맞는 HTML 작성 : title태그로 제목을 기재하고 div태그로 줄을 바꾸고, strong과 em태그로 강조하는 등 적절한 HTML 소스 적용
* 구체적인 페이지 제목 만들기 : 페이지 제목은 구체적이고 간결하게 구성하여 검색 결과 화면에서 잘리지 않도록 하기 / 반복 키워드 삼가
* 메타 태그 활용 : meta name="keywords"나 meta name="description"과 같은 태그를 적절히 사용
* 이미지에 alt 속성 기재 : 이미지 속에 그려지 문자는 검색엔진에 의해 인식되지 않기 때문에 alt 속성에 텍스트를 기재
### SPA에서 SEO 문제
* SAP는 기본적으로 CSR 동작 방식이고, CSR 에서는 View를 그릴 때 서버에서 js를 가져와 View 그려냄
* 하지만 SEO는 기본적으로 HTML에서 데이터를 수집하도록 동작되기 때문에 js를 가져오는 CSR에서는 데이터를 수집하지 못해 빈 페이지를 크롤링 하는것으로 인식

# DOM vs Virtual DOM
## DOM (Document Object Model) 이란?
* 웹 페이지에 대한 프로그래밍 인터페이스로 HTML 요소들이 구조화된 것
* DOM의 개체 구조는 트리 형태로 표현
* DevTools에 보여지는 코드들이 DOM임
* HTML과 DOM은 항상 동일하다고 볼 수 없음
    * 브라우저가 HTML을 수정했을 때 HTML과 DOM은 다름 ex) tbody와 같은 태그를 뺴먹고 table에 작성을 했을 때 브라우저가 tbody를 작성하여 DOM에 들어있음
    * script나 비동기로 DOM을 조작한 경우 ex) script나 비동기로 html내용을 변경하거나 컨테르르 가져올 경우
## Virtual DOM 이란?
* 실제 DOM을 추상화 한 것으로 매번 컴포넌트를 변경할 때 마다 DOM에 접근하여 조작하지 않고 변경 부분만 파악하여 일괄 업데이트 하기 위해 사용
* 실제 DOM은 브라우저에서 관리하지만 Virtual DOM은 실제 DOM을 카피하여 메모리상에서 관리함
* 메모리상에서 관리되고 있기 때문에 훨씬 빠르고 변경 부분을 파악후 리플로우를 한 번 일어나게 만들어 줌

# ESLint / Prettier
## ESLint 란?
* 사용자가 정의한대로 코드 작성을 하지 않을 시 에러를 표시해주는 코드 점검 플러그인으로 다수의 개발자들이 프로젝트를 진행할 때 하나의 코딩 스타일을 적용하여 한 명이 개발한 것과 같은 결과를 만들어주는 역할을 수행
## Prettier 란?
* 사용자의 옵션에 따라 코드를 다시 포맷팅하는 코드 포맷터로 탭 너비 등과 같은 코딩 스타일을 통일시키기 위해 사용

# React 클래스형 vs 함수형
## 클래스형
* contructor, this 등의 사용으로 인해 코드가 길고 복잡함
* 메모리 자원을 함수형보다 더 사용 (많이 차이나지는 않음)
* state관리 및 라이프사이클 메서드 사용 가능
## 함수형
* 구현하기 위한 코드 작성이 간편함
* 메모리 자원을 클래스형보다 덜 사용
* state관리 및 라이프사이클 메서드 사용 불가
* state관리 및 라이프사이클 관련 없는 코드에는 보통 함수형을 작성 / 관련 있는 코드에는 클래스형을 작성했었음 (Hooks의 등장으로 클래스형 사용 불핋요)
## 라이프사이클 이란?
* 컴포넌트가 생성되어 사용되고 소멸할 때 까지의 일련의 과정
## React 라이프사이클 메서드
* 라이프사이클 다이어그램
    <image src=image/reactlifecycle.PNG alt="reactlifecycle" width="800px" height="650px">
* getDerivedStateFromProps : props 로 받아온 것을 state 에 넣어주고 싶을 때 사용하는 메서드
    ```
    static getDerivedStateFromProps(nextProps, prevState) {
        console.log("getDerivedStateFromProps");
        if (nextProps.color !== prevState.color) {
            return { color: nextProps.color };
        }
        return null;
    }
    ```
* componentDidMount : 컴포넌트가 처음 렌더링을 마치고 실행되는 메서드
* shouldComponentUpdate : 컴포넌트가 리렌더링 할지 말지를 결정하는 메서드
    ```
    shouldComponentUpdate(nextProps, nextState) {
        console.log("shouldComponentUpdate", nextProps, nextState);
        // 숫자의 마지막 자리가 4면 리렌더링하지 않습니다
        return nextState.number % 10 !== 4;
    }
    ```
* getSnapshotBeforeUpdate : 컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 반환하면 그 다음 발생하게 되는 componentDidUpdate 함수에서 받아와서 사용 가능
    ```
    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log("getSnapshotBeforeUpdate");
        if (prevProps.color !== this.props.color) {
            return this.myRef.style.color;
        }
        return null;
    }
    ```
* componentDidUpdate : 리렌더링이 마치고, 화면에 우리가 원하는 변화가 모두 반영되고 난 뒤 호출되는 메서드
    ```
    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log("componentDidUpdate", prevProps, prevState);
        if (snapshot) {
            console.log("업데이트 되기 직전 색상: ", snapshot);
        }
    }
    ```

# React 상태관리
## 상태관리란 ?
* 여러 컴포넌트들 간의 데이터 전달 및 이벤트 통신을 한 곳에서 관리하여 일관된 데이터를 유지하게 하는 것
## 사용이유
* 작은 규모의 프로젝트에서는 크게 문제가 없지만 큰 규모의 프로젝트에서는 부모-자식 간의 state값을 지속적으로 전달하는데 비 효율을 가져오기 때문에 전역으로 관리하여 데이터 관리를 효율적으로 하기 위해서 사용
## React 상태관리 종류
* Redux, MobX와 같은 상태관리 라이브러리 사용
## Redux 동작원리
* action에 type과 전달할 데이터를 지정하여 정의
* reducer에 초기값과 함께 타입에 맞는 코드 실행 정의
* Prodiver store에 정의한 reducer 저장
* dispatch를 통해 action 실행
* useSelector를 통해 redux의 state값 불러와서 사용

# Redux 미들웨어
## Redux 미들웨어 란?
* 액션이 디스패치 되어 리듀서에서 처리되기 전에 지정된 특정 작업들을 수행할 수 있도록 설정하는 곳으로 보통 비동기 처리를 수행함
## Redux 미들웨어 종류
* Redux-Saga / Redux-Thunk / Redux-Observable 등
## Redux-Saga vs Redux-Thunk
* Thunk는 비동기 처리를 하는 구문의 액션이 Store에 저장하는 액션과 형태가 다름
* Saga는 비동기 처리를 하는 구문의 액션과 Store에 저장하는 액션의 구문이 유사함
* 이런 이유들로 Saga가 Thunk에 비해 액션 처리의 통일성을 가져와주고 또한 비동기 처리 하는 공간을 구분시키기 때문에 공간 구분이 명확함
* Thunk는 Reducer를 거치지 않고 비동기 처리를 하기 때문에 Store에 데이터를 저장하기 위해서는 Thunk 내부에서 다시 dispatch를 수행해야 함
* Saga는 Reducer를 거치고 비동기 처리를 하기 때문에 Store저장을 다시 수행하지 않아도 되고 이와 더불어 Store저장과 비동기 처리를 같은 타입으로 동시에 수행할 수 있음
## Generator 란?
* 함수실행을 중간에 멈췄다가 사용자가 원할 때 재개 할 수 있는 함수로 함수 이름 앞에 *룰 붙여서 코드를 작성함
* Redux-Saga에서 비동기처리를 하는 함수를 작성할 때 사용

# Vue 라이프사이클
* 라이프사이클 다이어그램
    <image src=image/vuelifecycle.PNG alt="vuelifecycle" width="800px" height="650px">
* beforeCreate : data속성과 methods속성이 아직 인스턴스에 정의되어 있지 않고 DOM과 같은 화면 요소에도 접근 불가능
* created : data속성과 methods속성이 정의되어 methods 속성값에 접근하여 로직 수행 가능, 하지만 인스턴스가 화면에 부착되기 전이라 template속성에 정의된 DOM 요소에는 접근 불가능
* beforeMount : template속성에 지정한 마크업 속성은 render() 함수로 변환 후 el속성에 지정한 화면요소에 인스턴스를 부착하기 전 호출되는 단계
* mounted : el 속성에서 지정한 요소에 인스턴스가 부착된 후 호출되는 단계
* beforeUpdate : watch로 관찰하고 있는 데이터가 변경되어 Virtual DOM으로 화면을 다시 그리기 전 단계
* updated : 데이터가 변경되어 다시 화면을 그리고 난 후의 단계

# Vue 상태관리
## Vue 상태관리 종류
* VueX 같은 상태관리 라이브러리사용
## VueX 동작원리
* 흐름도
    <image src=image/vuexflow.PNG alt="vuexflow" width="800px" height="650px">
* Vuex store를 정의하여 초기 state값 저장
* actions에 type과 commit내용 작성
* mutations에서 type과 전달받은 데이터를 state값에 저장
* 컴포넌트에서 dispatch를 통해 actions 실행
* store에서 원하는 state를 가져와 사용

# Vue 양방향바인딩
## 양방향바인딩 이란?
* 렌더링할 때 데이터를 view에게 보내주기만 하던 단반향바인딩과는 달리 view에서도 데이터의 변화를 감지하여 데이터가 변화됨과 동시에 view에 변경된 데이터가 표현되는 것

# Vue vs React
## Vue
* 컴포넌트 정의를 하나의 바구니에 담으려고 하는 구조를 가지고 있음
* HTML 기반으로 가독성이 좋으며 러닝커브가 낮은편
* 호환성에 중점을 두었기 때문에 vue 자체만으로 실행 가능
## React
* 역할군을 분리하기에 용이하여 대규모 프로젝트에 유용
* JSX 기반으로 기존 HTML과 다르기 때문에 문법적인 오류가 발생하여 Babel과 같은 트랜스파일러가 필요
* 자유성과 유연한 개발에 중점을 둠
* 사용자가 많기 때문에 개발 레펀러스가 방대
* 기업단위에서 관리하고 있기 때문에 버전관리가 빠름
* web과 native가 병행이 가능

# 타입스크립트
## 타입스크립트 란?
* 자바스크립트의 기능에 타입체크 기능이 추가된 것으로 자바스크립트에서 발생할 수 있는 타입과 관련된 에러를 미리 방지해주는 역할 수행
## 타입스크립트 등장배경
* 대규모 프로젝트에서 동적타입언어인 자바스크립트를 사용할 때 발생하는 문제점들에 대응하기 위해 등장, 대표적으로 코드를 작성할 때는 문제가 되지 않지만 코드를 실행시킬 때 발생하는 문제들을 대규모 프로젝트에서는 발견하기가 쉽지 않음
## 장점
* 정적타입을 지원 : 컴파일 단계에서 오류를 파악할 수 있음
* IDE의 지원 : IDE에서 타입 정보를 제공함으로 써 높은 수준의 타입체크, 리팩토링 등을 지원받을 수 있음
    * 리팩토링 : 외부동작은 바뀌지 않으면서 내부코드를 변경하는 것, 코드의 간결화 / 가독성을 높이기 위해 수행
* ES7 이하의 표준을 포함하고 있어 바벨과 같은 트랜스파일러를 사용할 필요가 없음
* class, interface, extends등을 사용하는 객체지향 프로그래밍을 지원 : 객체지향 언어에 익숙한 개발자들의 진입 장벽을 낮춤
* 엄격한 null check
## 단점
* 타입을 일일이 체크를 해야되는 번거로움이 있음
* interface 등의 추가적인 코드 작성으로 인해 코드가 길어지며 가독성이 떨어짐

가비지컬렉터

HTML이 렌더링중에 Javascript가 실행되면 렌더링이 멈추는 이유

Javascript 성능 최적화 방법

깃 관리 방식

배포경험

AMP란 무엇인가
기존것의 비해 장점은 무엇인가

프론트엔드에서 보안관련 이슈

Wireshark란?

redux vs mobx 관련 url
https://blog.rhostem.com/posts/2019-07-22-mobx-v6-and-react-v16-8
https://byseop.netlify.app/mobx-tutorial01/
https://velopert.com/3707
https://velog.io/@velopert/MobX-2-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-MobX-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-oejltas52z<file_sep>
public class Programmers_징검다리 {
	public static void main(String[] args) {
		int distance = 25;
		int rocks[] = {2,14,11,21,17};
		int n = 2;

		System.out.println(solution(distance, rocks, n));
	}

	public static int solution(int distance, int[] rocks, int n) {
        quickSort(0, rocks.length-1, rocks);

        int left = 1;
        int right = distance;
        int res = 0;

        while(left <= right) {
            int mid = (left + right) / 2; // 거리 최소값 중 최대값
            int prev = 0;
            int cnt = 0;

            for(int i=0; i<rocks.length; i++) {
                if(rocks[i] - prev < mid) {
                    cnt++;
                } else {
                    prev = rocks[i];
                }
            }

            if(cnt > n) {
                right = mid-1;
            } else {
                left = mid+1;
                res = Math.max(res, mid);
            }
        }

        return res;
    }

    public static void quickSort(int start, int end, int rocks[]) {
        if(start >= end) {
            return;
        }

        int pivot = partition(start, end, rocks);
        quickSort(start, pivot-1, rocks);
        quickSort(pivot+1, end, rocks);
    }

    public static int partition(int start, int end, int rocks[]) {
        int p = start-1;

        for(int i=start; i<end; i++) {
            if(rocks[end] > rocks[i]) {
                int temp = rocks[++p];
                rocks[p] = rocks[i];
                rocks[i] = temp;
            }
        }

        int temp = rocks[++p];
        rocks[p] = rocks[end];
        rocks[end] = temp;

        return p;
    }
}
<file_sep>import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Programmers_줄서는방법 {
	public static void main(String[] args) {
		System.out.println(Arrays.toString(solution(4, 6)));
	}

    public static int[] solution(int n, long k) {
    	List<Integer> list = new ArrayList<Integer>();
        int res[] = new int[n];
        int select = 0;
        long fac = 1;

        for(int i=1; i<=n; i++) {
            fac = fac * i;
            list.add(i);
        }

        k--;
        while(n > 0) {
            fac = fac / n--;
            res[select++] = list.remove((int)(k/fac));
            k = k%fac;
        }

        return res;
    }
}
<file_sep>import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Programmers_호텔방배정 {
	public static void main(String[] args) {
		System.out.println(Arrays.toString(solution((long)10, new long[]{1,3,4,1,3,1})));
	}

	public static long[] solution(long k, long[] room_number) {
		long res[] = new long[room_number.length];
        Map<Long, Long> indexMap = new HashMap<Long, Long>();
        List<Long> save = new ArrayList<Long>();

        for(int i=0; i<room_number.length; i++) {
            long num = room_number[i];
            save.clear();

            while(indexMap.get(num) != null) {
                save.add(num);
                num = indexMap.get(num);
            }

            res[i] = num;
            for(int j=0; j<save.size(); j++) {
                indexMap.put(save.get(j), num+1);
            }
            indexMap.put(num, num+1);
        }

        return res;
	}
}
<file_sep>## 기본설정
* VS Code를 실행시키고 왼쪽바의 맨 아래 Extension에서 다음 파일들 설치
    * Live Server
    * Vue 2 Snippets
    * Prettier
    * Auto Rename Tag
    * Vue Peek
    * Vetur
    * Bracket Pair Colorizer
* 크롬 웹스토어에에서 확장 프로그램 추가
    * Vue.js devtools
* Prettier 설정방법
    * ctrl + , 을 누르면 설정파일이 나옴
    * 맨 오른쪽 상단에 위치한 곳에서 사진에 보이는 것중 맨 왼쪽꺼 클링

    * json파일이 나오는데 다음 사진과 같이 등록

    * VS Code 재시작
* Node.js 설치
    * https://nodejs.org/ko/ 로 접속 or 검색창에 Node.js 검색
* yarn 설치
    * VS Code 상단바에 있는 Terminal을 눌러 New Terminal or 키보드로 ctrl + shift + ` 입력
    * terminal에서 npm install -g yarn 입력
* power shell 보안설정
    * window 검색창에 power shell을 검색하여 관리자 권한으로 실행
    * Set-ExecutionPolicy RemoteSigned 입력
* vue cli 설치
    * yarn global add @vue/cli or npm install -g @vue/cli
* vue project 생성
    * vue create 뷰 프로젝트 이름 -> ex) vue create first
    * default 선택
    * 프로젝트를 생성하고 package.json을 들어가면 script에서 실행가능한 명령들 확인 가능, serve 뒤에 "--open --port 포트"를 입력하면 해당 포트로 바로 실행 가능 -> ex) --open --port 9090
* yarn serve로 vue 실행

## axios 사용하기

* terminal에 npm install --save axios 입력
* yarn을 설치하였을 경우 yarn add axios 입력도 가능
* 원하는 곳에 사진과 같이 axios를 import 해주고 사용 (axios 종류 => axios.get, axios.put, axios.post, axios.delete)

## vue router 사용하기

* terminal에 npm install --save vue-router 입력
* yarn을 설치하였을 경우 yarn add vue-router 입력도 가능
* main.js의 상단에 import VueRouter from 'vue-router', 비어있는 곳에 Vue.use(VueRouter) 입력

* 사진과 같이 assets에 router.js를 만들고 router로 사용할 컴포넌트들을 등록

* main.js의 상단에 router.js의 위치를 찾아 import해주고, new Vue 안에 router 입력

* router를 사용하고 싶은 곳에 <router-view></router-view>로 router위치 등록, 등록된 위치에 router를 가져오는 방법은 this.\$router.push를 사용

## vuetify 사용하기

* terminal에 npm install vuetify 입력
* yarn을 설치하였을 경우 yarn add vuetify 입력도 가능
* terminal에 vue add vuetify 입력
    * default 선택
    * 주의해야 될점: App.vue가 싹 갈리니 미리 작성한 내용들이 있다면 백업을 해야 됨
    * 실행이 완료되면 App.vue부터 시작해서 여러가지 파일들이 자동생성

## vuex 사용하기

* terminal에 npm install vuex 입력
* yarn을 설치하였을 경우 yarn add vuex 입력도 가능
* 사진과 같이 assets에 vuex.js를 만들고 state, mutation, action을 등록

* Constant는 상수값을 저장하기 위한 용도, 다음과 같이 asset에 Constant.js를 만들고 등록

* main.js의 상단에 vuex위치를 찾아 store로 저장하고 new Vue 안에 vuex 입력

* vuex의 state 값에 데이터 저장 방법 (dispatch 사용)

* vuex의 state 값에 있는 데이터 불러오는 방법

## moment 사용하기
* js에서 시간과 관련된 값을 구하기 위해 moment를 사용
* terminal에 npm install moment --save 입력
* yarn을 설치하였을 경우 yarn add moment  입력도 가능
* 사용방법은 axios와 동일
* 원하는 곳에 사진과 같이 moment를 import 해주고 사용

* format에 관한 자세한 내용은 https://momentjs.com/ 참조

## router 새로고침하기
 * this.$router.push(...) 대신에 this.$router.go()를 사용

## jquery 사용하기
* terminal에 npm install --save jquery 입력
* yarn을 설치하였을 경우 yarn add jquery 입력도 가능
    <file_sep>"use strict";
var imgCoords = '0';
var example = {
    a: 3,
    b: 7,
    d: 100,
};
var rsp = {
    ROCK: '0',
    SCISSORS: '-142px',
    PAPER: '-284px',
};
var score = {
    ROCK: 0,
    SCISSORS: 1,
    PAPER: -1,
};
function computerChoice(imgCoords) {
    return Object.keys(rsp).find(function (k) { return rsp[k] === imgCoords; });
}
var interval;
var point = 0;
document.querySelectorAll('.btn').forEach(function (btn) {
    btn.addEventListener('click', function (e) {
        clearInterval(interval);
        setTimeout(intervalMarker, 2000);
        var myChoice = this.textContent;
        var myScore = score[myChoice];
        var computerScore = score[computerChoice(imgCoords)];
        var diff = myScore - computerScore;
        if (diff === 0) {
            console.log('비겼습니다.');
        }
        else if ([-1, 2].includes(diff)) {
            console.log('이겼습니다!!');
            point++;
        }
        else {
            console.log('졌습니다!!');
            point--;
        }
        document.querySelector('#point').textContent = String(point);
    });
});
function intervalMarker() {
    interval = setInterval(function () {
        if (imgCoords === rsp.ROCK) {
            imgCoords = rsp.SCISSORS;
        }
        else if (imgCoords === rsp.SCISSORS) {
            imgCoords = rsp.PAPER;
        }
        else {
            imgCoords = rsp.ROCK;
        }
        if (document.querySelector('#computer')) {
            document.querySelector('#computer').style.background = "url(https://en.pimg.jp/023/182/267/1/23182267.jpg) " + imgCoords + " 0";
        }
    }, 100);
}
intervalMarker();
<file_sep>import java.util.Stack;

public class Programmers_짝지어제거하기 {
	public static void main(String[] args) {
		System.out.println(solution("baabaa"));
		System.out.println(solution("cdcd"));
	}

	public static int solution(String s) {
        Stack<Character> stack = new Stack<Character>();

        for(int i=0; i<s.length(); i++) {
            char temp = s.charAt(i);
            if(stack.size() == 0) {
                stack.push(temp);
            } else {
                char top = stack.pop();
                if(temp != top) {
                    stack.push(top);
                    stack.push(temp);
                }
            }
        }

        return stack.size() == 0 ? 1 : 0;
    }
}
<file_sep># UTF-8 인코딩 필터 설정 (web.xml에 추가)
    ```
    <filter>
    	<filter-name>encodingFilter</filter-name>
    	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    	<init-param>
    		<param-name>encoding</param-name>
    		<param-value>utf-8</param-value>
    	</init-param>
    </filter>

    <filter-mapping>
    	<filter-name>encodingFilter</filter-name>
    	<url-pattern>/*</url-pattern>
    </filter-mapping>
    ```

# 초기 시작페이지 설정 (web.xml에 추가)
    ```
    <welcome-file-list>
     	<welcome-file>hello.jsp</welcome-file> <!-- 처음 시작페이지를 hello.jsp 페이지로 설정 -->
     </welcome-file-list>
    ```

# Jquery 사용하기
## CDN 등록하기
* Jquery CDN 가져오기(아래쪽에 구글CDN 선택) : https://jquery.com/download/
* 3.x snippet에 있는 script를 복사하여 jsp 파일에 붙여넣기
    ```<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>```

## Jquery 자동완성 플러그인 설치 (Eclipse, STS 사용자)
* 툴 상단의 Help -> Install New Software... -> Work with에 ```http://oss.opensagres.fr/tern.repository/1.2.0/``` 복붙 한 뒤 Embedded, IDE, Tooling 선택 후 next 및 finish
* 재시작 Alert가 나올때 까지 종료하지 않고 기다리기
* 재시작 후 적용시키고 싶은 프로젝트 우클릭 -> Configure -> Convert to Tern Project -> jquery, jquery extension 클릭

## on click 사용법
    ```
    <body>
    <input type="button" value="회원가입" id="signup">
    </body>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script type="text/javascript">
        $("#signup").on("click", function() {
            console.log("회원가입");
        })
    </script>
    ```

## ajax 사용법
    ```
    let data = {
                authorno: $("#authorno").val(),
                description: $("#description").text(),
                isbn: $("#isbn").val(),
                price: $("#price").val(),
                publisher: $("#publisher").val(),
                title: $("#title").val()
            };

    $.ajax({
        url: "/rest/book",
        type: "post",
        data: JSON.stringify(data),
        contentType: "application/json",
        success: function(resTxt) {
            console.log(res);
            if(res.status) {
                list("추가 성공");
            } else {
                list("추가 실패");
            }
        },
        error: function(error) {
            list("추가 실패");
            console.log(error);
        }
    });
    ```<file_sep># Sofeware Engineering

## 소프트웨어 생명주기 모델
### 소프트웨어 생명주기 모델이란 ?
* 소프트웨어를 기획부터 개발, 폐기까지 하나의 주기를 보고 이를 효과적으로 관리하기 위한 절차
### 구성
* 요구명세화 -> 분석 -> 설계 -> 개발 -> 시험 -> 유지보수
### 종류
* 폭포수 모델
  * 개념도

  * 특징: 단계적, 순차적 접근
  * 장점: 이해가 용이하고 관리가 편함
  * 단점: 요구분석이 어려움
* 프로토타이핑 모델
  * 개념도

  * 특징: 프로토타입을 개발
  * 장점: 요구분석이 용이, 개발타당성 검증 가능
  * 단점: 프로토타입 폐기에 대한 비용 증가
* 나선형 모델
  * 개념도

  * 특징: 위험분석, 반복적인 개발
  * 장점: 위험성 감소와 변경에 유여한 대처
  * 단점: 단계반복에 따른 공정관리의 어려움
* 반복적 모델
  * 개념도

  * 특징: 증분을 병행 개발
  * 장점: 병행개발로 인한 일정 단축 가능
  * 단점: 병행개발에 따른 관리비용 증가

## 애자일 개발 방법론
### 애자일 개발 방법론이란 ?
* 소프트웨어 개발방법에 있어서 아무런 계획이 없는 개발방법과 계획이 지나치게 많은 개발 방법들 사이에서 타협점을 찾고자 하는 방법론

### 특징
* 계획을 통해서 주도해 나갔던 방법론들과는 달리 앞을 예측하며 개발을 하지 않고, 일정주기를 가지고 끊임없이 프로토타입을 만들어내며 그때 그때 필요한 요구를 더하고 수정하여 하나의 커다란 소프트웨어를 개발해 나감

### 애자일 개발 방법론의 장단점
* 장점
  * 프로젝트 진행 중간 중간에 필요한 요소들을 변경할 수 있음
  * 시작할 때 프로젝트를 정확하게 규정하지 않아도 됨
  * 작은 요소들을 출시 할 때 빠르게 만들기가 가능
  * 점진적으로 테스트되기 때문에 초기에 버그 발견이 가능
* 단점
  * 시간이 오래 걸림

### 폭포수 VS 애자일
  * 개념도 차이

  * 애자일이 각광받는 이유
    * 가벼운 개발 방법을 통해 고객과의 협력을 강조하고, 과정과 도구 보다는 개인과 상호작용을 더 중요시하며, 계획을 따르는 것보다 변화에 대처하기 때문<file_sep>
public class Programmers_땅따먹기 {
	public static void main(String[] args) {
		System.out.println(solution(new int[][]{{1,2,3,5},{5,6,7,8},{4,3,2,1}}));
	}

	public static int solution(int[][] land) {
        int sum[][] = new int[land.length][4];
        for(int j=0; j<land[0].length; j++) {
            sum[0][j] = land[0][j];
        }

        for(int i=1; i<land.length; i++) {
            for(int j=0; j<land[i].length; j++) {
                for(int k=0; k<land[i].length; k++) {
                    if(k == j) {
                        continue;
                    }

                    sum[i][j] = Math.max(sum[i][j], sum[i-1][k]);
                }
                sum[i][j] = sum[i][j] + land[i][j];
            }
        }

        int res = 0;

        for(int j=0; j<land[0].length; j++) {
            res = Math.max(res, sum[land.length-1][j]);
        }

        return res;
    }
}
<file_sep>1. npm init => author: ZeroCho, license: MIT
2. npm install react react-dom next
3. npm install -D nodemon webpack
4. npm install -D eslint
5. 폴더에 .eslintrc 생성
6. .eslintrc 작성 다하면 npm i eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks
7. npm install -g next
8. npm install antd

# proptypes설치
 * npm install prop-types

# 기본구성
 ### _document.js => html, head, body
 ### _app.js => root
 ### pages => 실제 컴포넌트
 ### _error.js => 에러가 발생할 때 (배포하기 전에는 굳이 커스터마이징 할 필요가 없음)

# redux 설치
 * npm install redux react-redux
 * npm install react-redux@next

# next에서 redux 사용할 때
 * npm install next-redux-wrapper

# redux-saga 설치
 * npm install redux-saga<file_sep>export const initialState = {
    mainPosts: [{
        User: {
            id: 1,
            nickname: '제로초',
        },
        content: '첫 번째 게시글',
        img: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxISEhUSEhIVFRUSFRUVFhUVFRUVFxUVFRUWFhUWFRUYHSggGBolHRcVITEhJSkrLi4uFx8zODMtNygtLisBCgoKDg0OGhAQGyslICUtLS8tLS0tLS0tKy0tLSstLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIALcBEwMBIgACEQEDEQH/<KEY>Qj/<KEY>sc<KEY>elnTGdeh68E9gfBXEhSBTcjkQI1I5Mkr2RybBRGnr1EkUp5UWQqWXISIUqjvT0p1TDysNI0oWgcLUHZxN01OpVMwum7SZreV7n0WmU8eVq9T/jobuZB1s+InEoWee0atDWZOq0Kc2BKyHjqoEk1r7Feq+CCK3Kc6EWJUWaPoij2i9gosrB96AeiA2RzToVJgxQjdcPjUd8aBpBpsPU+JMdvY+ak/R4n+HkqoWp6GrezY3S9HoPV7Ds+Bg7WKE1OC<KEY>
    }],
    imagePaths: [],
};

const ADD_POST = 'ADD_POST';
const ADD_DUMMY = 'ADD_DUMMY';

const addPost = {
    type: ADD_POST,
};

const addDummy = {
    type: ADD_DUMMY,
    data: {
        content: 'Hello',
        UserId: 1,
        User: {
            nickname: '제로초',
        },
    },
};

const reducer = (state = initialState, action) => {
    switch(action.type) {
        case ADD_POST: {
            return {
                ...state,
            };
        }

        case ADD_DUMMY: {
            return {
                ...state,
                mainPosts: [action.data, ...state.mainPosts],
            };
        }

        default: {
            return {
                ...state,
            }
        }
    }
};

export default reducer;<file_sep># Database

## 커넥션풀 (Connection pool)
### 커넥션풀이란 ?
* 구조

* 데이터베이스와 연결된 커넥션을 미리 만들어서 풀속에 저장해 두고 있다가 필요할 때 커넥션을 풀에서 쓰고 다시 풀에 반환하는 기법
* 웹 프로그램에서는 데이터베이스의 환경설정과 연결관리 등을 따로 xml파일이나 속성파일을 사용해서 관리
### 사용이유
* 사용자가 데이터베이스를 사용할 때마다 커넥션을 생성하여 연결해야 되는 비효율적인 문제를 해결하기 위해 사용
* 예를들어 웹사이트에 사용자가 게시판을 확인하고 게시물도 수정하고 새로운 게시물을 등록한다면 DB접속은 총 3번이 필요하다 (데이터 검색, 데이터 갱신, 데이터 삽입)
* 커넥션풀은 이렇게 3번의 커넥션을 생성하는 것 대신에 미리 생성된 커넥션을 빌려줌으로 써 기존에 풀에 생성된 커넥션을 제외하고는 커넥션 생성을 하지 않음
### 사용자가 풀안에 있는 커넥션보다 많다면 ?
* 현재 커넥션을 사용하고 있는 사용자가 커넥션을 풀에 반납할 때까지 기다리다가 들어온 사용자 순서대로 다시 커넥션을 빌려준다

## 데이터베이스 기본 개념
### 테이블 구성요소
* 릴레이션
  * 릴레이션은 말그대로 테이블을 의미
* 애트리뷰트 (속성)
  * 사람이라는 데이터로 가정할 때 이름, 나이 와 같은 구성요소
* 튜플 (레코드)
  * 한 명의 사람을 지칭, 특정 속성들을 이루고 있는 하나의 데이터
* 디그리 (차수)
  * 속성의 갯수
* 카디너리티
  * 튜플의 갯수

## 키 (Key)
### 키란 ?
* 데이터베이스 테이블에서 튜플을 찾을 때 서로 구분할 수 있는 기준이 되는 속성값들
### 키의 종류
* 슈퍼키
  * 테이블에서 튜플을 찾을 때 구분할 수 있는 기준이 되는 모든 속성값들의 집합
  * 유일성을 만족하지만 최소성을 만족하지 못한다
  * 유일성: 모든 튜플을 구분할 수 있는 정도
  * 최소성: 구분할 수 있는 모든 속성값들이 최솟값을 이루는 정도
* 후보키
  * 슈퍼키에서 최소성을 만족하는 키들의 집합
  * 유일성과 최소성을 모두 만족
* 기본키
  * 후보키에서 선택된 주 키
* 대체키
  * 후보키에서 기본키로 선택되지 못한 나머지 키
* 외래키
  * 다른 테이블의 속성을 참조하고 있는 키

## 정규화 (Normalization)
### 정규화란 ?
* 관계형 데이터베이스의 설계에서 중복을 최소화 하기 위해 테이블을 분리 시켜 데이터를 구조화하는 기법
* 불필요한 데이터를 없앨 수 있고, 삽입/삭제/갱신 시 발생할 수 있는 이상현상(Anamolies)들을 방지할 수 있음
### 1차 정규화
* 1차 정규화의 조건: 테이블의 모든 속성값을 원자 값으로 분리 (모든 컬럼이 1개의 데이터만 소유해야 함)
* 정규화 전

* 정규화 후

* Telephone Number속성에 존재하는 2개 이상의 데이터 들을 1개의 값들로 모두 분리
### 2차 정규화
* 2차 정규화의 조건: 종속관계에서 부분 함수적 종속 제거, 완전 함수적 종속으로만 이루어진 테이블로 구성
* 완전 함수적 종속: 모든 기본키에 종속이 되는 관계
* 부분 함수적 종속: 기본키의 일부분에 의해 종속이 되는 관계
* 정규화 전

* 정규화 후

* 기본키가 Manufacturer과 Model인데 Manufacturer Country속성 같은 경우 Manufacturer만으로도 종속관계가 이루어짐
* 부분 함수적 종속관계이므로 테이블을 분리하여 모든 테이블을 완전 함수적 종속관계로 변형
### 3차 정규화
* 3차 정규화의 조건: 종속관계에서 이행적 함수적 종속 제거
* 이행적 함수적 종속: A->B, B->C 일때 A->C를 만족하는 관계
* 정규화 전

* 정규화 후

* (Tournament, Year) -> (Winner), (Winner) -> (Winner Date of Birth)일 때 (Tournament, Year) -> (Winner Date of Birth) 관계가 성립 함
* 3차 정규화에 위반 되므로 테이블을 분리
### BCNF
* BCNF의 조건: 종속관계에서 후보키가 아닌 결정자 제거
* 정규화 전

* 정규화 후

* 후보키는 (학번, 과목명), (학번, 담당교수)이지만 존재하는 함수적 종속은 (학번, 과목명) -> (담당교수), (학번, 담당교수) -> (과목명), (담당교수) -> (과목명) 이 존재
* (담당교수) -> (과목명)에서 담당교수는 후보키가 아니기 때문에 테이블을 분리
* 대부분 아무리 많은 정규화를 해도 BCNF까지 한다.
### 4차 정규화
* 4차 정규화의 조건: 다중값 종속 제거
### 5차 정규화
* 5차 정규화의 조건: 테이블의 모든 조인이 후보키를 통해서만 성립

## 트랜잭션 (Transaction)
### 트랜잭션이란 ?
* 데이터베이스의 상태를 변화시키는 하나의 논리적 기능을 수행하는 단위
* 데이터베이스 시스템에서 병행제어 및 회복 작업 시 처리되는 작업의 논리적 단위
* 사용자가 시스템에 서비스 요구 시 시스템이 응답하기 위한 상태 변환 과정의 작업 단위
* 하나의 트랜잭션은 항상 Commit되거나 Rollback 된다
  * Commit: SQL문이 실행되어 데이터베이스에 적용되는 기법
  * Rollback: SQL문이 실행되기 이전의 값으로 데이터베이스의 상태를 되돌려 놓는 기법
### 트랜잭션의 성질
* 원자성(Atomicity)
  * 트랜잭션의 연산은 데이터베이스에 모두 반영되거나 모두 반영되지 않아야 한다
  * 작은 오류 하나라도 발생한다면 수행되지 않고 모두 취소가 되어야 한다
* 일관성(Consistency)
  * 트랜잭션이 실행을 성공적으로 완료하면 항상 일관성 있는 데이터베이스 상태로 변환되어야 한다
  * 시스템이 가지고 있던 요소들은 트랜잭션 적용 전과 후가 같아야 한다
* 독립성(Isolation)
  * 하나의 트랜잭션이 실행중일 때는 다른 트랜잭션이 간섭할 수 없다
  * 수행중인 트랜잭션이 완전히 종료될 때까지 다른 트랜잭션은 수행결과를 참조할 수 없다
* 지속성(Durablity)
  * 트랜잭션이 실행을 성공적으로 완료하면 시스템이 고장나더라도 영구적으로 반영되어야 한다

## 뷰 (View)
### 뷰란 ?
* 뷰는 사용자에게 접근이 혀용된 자료만을 제한적으로 보여주기 위해 하나 이상의 기본 테이블로부터 유도된 이름을 가지는 가상 테이블
* 기본테이블로부터 유도된 테이블이기 때문에 기본 테이블과 같은 형태의 구조를 사용하며 조작도 기본테이블과 거의 동일
### 뷰의 장단점
* 장점
  * 논리적 데이터 독립성을 제공
  * 동일 데이터에 대해 동시에 여러 사용자의 상이한 응용이나 요구를 지원
  * 사용자의 데이터 관리를 간단하게 함
  * 접근 제어를 통한 자동 보안
* 단점
  * 독립적인 인덱스를 소유할 수 없음
  * 뷰의 정의를 변경할 수 없기 떄문에 alter문을 사용할 수 없음
  * 구성내용에 따라 삽입, 삭제, 갱신의 제약이 따름
### 사용방법
  * create view {뷰이름}({속성1}, {속성2}, ...) as select ({속성1}, {속성2}, ...) from {테이블이름} {where 조건절}

## 인덱스 (Index)
### 인덱스란 ?
* 테이블의 동작 속도를 높여주기 위한 기법
* 많은 null값을 갖거나 중복이 많거나 데이터가 많을 때 인덱스를 사용하면 효과적임
* 반면에 테이블의 크기가 작은 곳에 인덱스를 만드는 것은 불필요
### 사용방법
  * alter table {테이블이름} add index({속성})<file_sep>{
    "compilerOptions": {
        "noImplicitAny": true,
        "noImplicitThis": true,
        "noImplicitReturns": true,
        "strict": true,
        "lib": ["DOM", "ES5", "ES6", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020"],
        "target": "ES5"
    },
    "include": ["lecture.ts"],
    "exclude": ["*.js"],
    // "extends": ""
}<file_sep>
public class Programmers_문자열압축 {
	public static void main(String[] args) {
		System.out.println(solution("aabbaccc"));
		System.out.println(solution("ababcdcdababcdcd"));
		System.out.println(solution("abcabcdede"));
		System.out.println(solution("abcabcabcabcdededededede"));
		System.out.println(solution("xababcdcdababcdcd"));
	}

	public static int solution(String s) {
		int res = Integer.MAX_VALUE;
        for(int k=1; k<=s.length()/2; k++) {
            String resString = "";
            String temp = "";
            int count = 0;

            for(int j=0; j<s.length(); j=j+k) {
                String a = s.substring(j, Math.min(j+k, s.length()));
                if(temp.equals("")) {
                    count = 1;
                } else if(temp.equals(a)) {
                    count++;
                } else {
                    if(count == 1) {
                        resString = resString + temp;
                    } else {
                        resString  = resString + count + temp;
                    }
                    count = 1;
                }
                temp = a;
            }

            if(count == 1) {
            	resString = resString + temp;
            } else if(count > 1) {
            	resString = resString + count + temp;
            }

            res = Math.min(res, resString.length());
        }

        return s.length() == 1 ? 1 : res;
    }
}
<file_sep>
public class Programmers_스티커모으기_2 {
	public static void main(String[] args) {
		System.out.println(solution(new int[]{14,6,5,11,3,9,2,10}));
		System.out.println(solution(new int[]{1,3,2,5,4}));
	}

	public static int solution(int sticker[]) {
        int n = sticker.length;
        int dp[][] = new int[n][2]; // [n][0]: 1번 스티커를 뜯을 경우, [n][1]: 1번 스티커를 뜯지 않을 경우
        if(n > 1) {
            dp[0][0] = sticker[0];
            dp[1][0] = Math.max(sticker[0], sticker[1]);
            dp[1][1] = sticker[1];

            int max = dp[1][0];

            for(int i=2; i<n-1; i++) {
                dp[i][0] = Math.max(dp[i-1][0], dp[i-2][0] + sticker[i]);
                max = Math.max(max, dp[i][0]);
            }

            for(int i=2; i<n; i++) {
                dp[i][1] = Math.max(dp[i-1][1], dp[i-2][1] + sticker[i]);
                max = Math.max(max, dp[i][1]);
            }

            return max;
        } else {
            return sticker[0];
        }
    }
}
<file_sep>import {all, fork, takeLatest, call, put} from 'redux-saga/effects';
import { LOG_IN, LOG_IN_SUCCESS, LOG_IN_FAILURE} from '../reducers/user';

function loginAPI() {
    // 서버에 요청을 보내는 부분
}

function* login() {
    try {
        yield call(loginAPI);
        yield put({ // put은 dispatch와 동일
            type: LOG_IN_SUCCESS
        });
    } catch(e) { // loginAPI 실패
        console.log(e);
        yield put({
            type: LOG_IN_FAILURE,
        });
    }
}

function* watchLogin() {
    yield takeLatest(LOG_IN, login)
}

export default function* userSaga() {
    yield all([
        fork(watchLogin),
    ]);
}<file_sep># 개념
* IoC (Inversion of Control) ?
   * ~

* DI (Dependency Injection) ?
   * ~

* Bean ?
   * ~

* MVC pattern ?
   * ~

* MVVM pattern ?
   * ~

* DispatcherServelet ?
   * ~

* DTO ?
   * ~

* Annotation ?
   * ~

* Configuration ?
   * ~

* Repository ?
   * ~

* Service ?
   * ~

# 프로젝트 생성
* STS 설치
* Servers에 Tomcat 가져오기
* 마우스 우클릭 -> new -> Spring Legacy Project -> Spring MVC Project -> 이름 작성 -> 기본 패키지 설정 (ex) sc.video.chat)
* /src/main/WEB-INF/web.xml에서 <java-version>과 하단의 maven-compiler-plugin의 configuration <source>와<target> 1.8로 변경
* 생성된 프로젝트 우클릭 -> Maven -> Update Project
* 생성된 프로젝트 우클릭 -> Run As -> Run on Server -> 위에서 가져온 Tomcat 클릭한 뒤 Finish 하면 default로 만들어지는 home.jsp가 실행된 창 오픈

# STS에서 Run on Server로 실행되는 브라우저 변경
* STS 상단바의 Window -> Preferences -> 'browser' 검색 -> General의 Web Browser -> Use external web browser 선택 및 실행될 web browser 선택

# pom.xml에 dependency 변경 및 추가 (pom.xml의 내용이 변경되면 항상 Update Project 수행)
* https://mvnrepository.com/ 에 접속하여 검색을 통해 dependency 찾기 가능
* 추가한 dependency
   * spring-jdbc : java에서 DB에 접근할 수 있도록 해줌
   * spring-tx : DB transaction 처리 역할을 수행
   * mysql-connector-java : mysql 연결을 위한 설정
   * mybatis, mybatis-spring : mybatis를 사용하여 java와 mysql을 연동
   * commons-dbcp : DB의 커넥션풀을 생성하여 DB를 사용할 수 있는 환경 제공
   * jackson-databind : JSON구조를 처리해주는 라이브러리
   * lombok : DTO를 효율적으로 설정하게 해주는 라이브러리 (인터넷에 검색하여 따로 추가적인 설치파일 설치하기)
   * springfox-swagger-ui, springfox-swagger2 : 구조화 된 Spring이 잘 동작하는지 확인할 수 있는 환경 제공
* 변경 및 추가를 통한 최종 pom.xml
   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>sc.video</groupId>
      <artifactId>chat</artifactId>
      <name>Spring</name>
      <packaging>war</packaging>
      <version>1.0.0-BUILD-SNAPSHOT</version>
      <properties>
         <!-- Java -->
         <java-version>1.8</java-version>

         <!-- Spring -->
         <org.springframework-version>5.2.8.RELEASE</org.springframework-version>

         <!-- Aspect -->
         <org.aspectj-version>1.9.6</org.aspectj-version>

         <!-- Slf4j -->
         <org.slf4j-version>1.7.5</org.slf4j-version>
      </properties>
      <dependencies>
         <!-- Spring -->
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
               <!-- Exclude Commons Logging in favor of SLF4j -->
               <exclusion>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
               </exclusion>
            </exclusions>
         </dependency>

         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
         </dependency>

         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework-version}</version>
         </dependency>

         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${org.springframework-version}</version>
         </dependency>

         <!-- DB -->
         <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
         </dependency>

         <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
         </dependency>

         <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
         </dependency>

         <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.7.0</version>
         </dependency>

         <!-- Binding -->
         <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.1</version>
         </dependency>

         <!-- Lombok -->
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
         </dependency>

         <!-- Swagger -->
         <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
         </dependency>

         <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
         </dependency>

         <!-- AspectJ -->
         <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
         </dependency>

         <!-- Logging -->
         <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
         </dependency>
         <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
         </dependency>
         <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
         </dependency>
         <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
               <exclusion>
                  <groupId>javax.mail</groupId>
                  <artifactId>mail</artifactId>
               </exclusion>
               <exclusion>
                  <groupId>javax.jms</groupId>
                  <artifactId>jms</artifactId>
               </exclusion>
               <exclusion>
                  <groupId>com.sun.jdmk</groupId>
                  <artifactId>jmxtools</artifactId>
               </exclusion>
               <exclusion>
                  <groupId>com.sun.jmx</groupId>
                  <artifactId>jmxri</artifactId>
               </exclusion>
            </exclusions>
            <scope>runtime</scope>
         </dependency>

         <!-- @Inject -->
         <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
         </dependency>

         <!-- Servlet -->
         <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
         </dependency>
         <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
         </dependency>
         <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
         </dependency>

         <!-- Test -->
         <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
         </dependency>
      </dependencies>
      <build>
         <plugins>
               <plugin>
                  <artifactId>maven-eclipse-plugin</artifactId>
                  <version>2.9</version>
                  <configuration>
                     <additionalProjectnatures>
                           <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                     </additionalProjectnatures>
                     <additionalBuildcommands>
                           <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                     </additionalBuildcommands>
                     <downloadSources>true</downloadSources>
                     <downloadJavadocs>true</downloadJavadocs>
                  </configuration>
               </plugin>
               <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>2.5.1</version>
                  <configuration>
                     <source>1.8</source>
                     <target>1.8</target>
                     <compilerArgument>-Xlint:all</compilerArgument>
                     <showWarnings>true</showWarnings>
                     <showDeprecation>true</showDeprecation>
                  </configuration>
               </plugin>
               <plugin>
                  <groupId>org.codehaus.mojo</groupId>
                  <artifactId>exec-maven-plugin</artifactId>
                  <version>1.2.1</version>
                  <configuration>
                     <mainClass>org.test.int1.Main</mainClass>
                  </configuration>
               </plugin>
         </plugins>
      </build>
   </project>
   ```

# 개발환경 설정 (xml)
## servlet-context.xml 설정
* web 연결과 관련된 설정들을 구성하는 파일 (View ↔ Controller 등)
* 파일위치 : src/main/webapp/WEB-INF/spring/appServlet
* Controller의 base package 등록 (패키지 이름을 Controller에 sc.video.chat.controller 사용 예정)
   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <beans:beans xmlns="http://www.springframework.org/schema/mvc"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:beans="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
         http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

      <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

      <!-- Enables the Spring MVC @Controller programming model -->
      <annotation-driven />

      <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
      <resources mapping="/resources/**" location="/resources/" />

      <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
      <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
         <beans:property name="prefix" value="/WEB-INF/views/" />
         <beans:property name="suffix" value=".jsp" />
      </beans:bean>

      <context:component-scan base-package="sc.video.chat.controller" />
   </beans:beans>
   ```
## root-context.xml 설정
* web 연결과 관련되지 않은 나머지를 구성하는 파일
* 파일위치 : src/main/WEB-INF/spring
* 하단 바의 Namespaces에서 context 클릭
* Repository와 Service의 base package 등록 (패키지 이름을 Repository는 sc.video.chat.repository, Service는 sc.video.chat.service 할 예정)
   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

      <!-- Uncomment and add your base-package here: -->
            <context:component-scan
               base-package="sc.video.chat.repository, sc.video.chat.service"/>

   </beans>
   ```

## web.xml 설정
* DispatcherServlet, ContextConfigLocation 등을 구성하는 파일
* 파일위치 : src/main/webapp/WEB-INF
* Encoding 설정하기
   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

      <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
      <context-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>/WEB-INF/spring/root-context.xml</param-value>
      </context-param>

      <!-- Creates the Spring Container shared by all Servlets and Filters -->
      <listener>
         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>

      <!-- Processes application requests -->
      <servlet>
         <servlet-name>appServlet</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
      </servlet>

      <servlet-mapping>
         <servlet-name>appServlet</servlet-name>
         <url-pattern>/</url-pattern>
      </servlet-mapping>

      <filter>
         <filter-name>encodingFilter</filter-name>
         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
         <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
         </init-param>
      </filter>

      <filter-mapping>
         <filter-name>encodingFilter</filter-name>
         <url-pattern>/*</url-pattern>
      </filter-mapping>

   </web-app>
   ```

## MySQL, MyBatis 설정
* 참고사이트 : https://mybatis.org/mybatis-3/ko/index.html / http://mybatis.org/spring/ko/getting-started.html
* src/main/resources에 jdbc.properties 파일 생성 후 mysql 정보 입력
   ```
   jdbc.driver=com.mysql.cj.jdbc.Driver
   jdbc.url=jdbc:mysql://localhost:3306/videochat?serverTimezone=UTC
   jdbc.user=root
   jdbc.password=<PASSWORD>
   jdbc.maxPoolSize=20
   ```
* root-context.xml 설정 변경
   ```
   <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:tx="http://www.springframework.org/schema/tx"
         xmlns:context="http://www.springframework.org/schema/context"
         xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

         <!-- Uncomment and add your base-package here: -->
               <context:component-scan
                  base-package="sc.video.chat.repository, sc.video.chat.service"/>

      <context:property-placeholder location="classpath:jdbc.properties" /> <!-- jdbc.properties에 등록된 변수 사용하기 위해 가져오기 -->

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!-- mysql 연동 bean 등록 -->
         <property name="driverClassName" value="${jdbc.driver}"></property>
         <property name="url" value="${jdbc.url}"></property>
         <property name="username" value="${jdbc.user}"></property>
         <property name="password" value="${<PASSWORD>}"></property>
      </bean>

      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- mybatis 사용을 위한 sessionFactory bean 등록 -->
         <property name="dataSource" ref="dataSource" />
         <property name="configLocation" value="classpath:spring/mybatis-config.xml" /> <!-- mybatis 설정파일 등록 -->
      </bean>

      <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!-- mybatis 사용을 위한 sqlSession bean 등록 -->
         <constructor-arg index="0" ref="sqlSessionFactory" />
      </bean>

      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- transaction 처리를 위한 bean 등록 -->
         <property name="dataSource" ref="dataSource"></property>
      </bean>

      <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 하단 Namespaces 바를 클릭하여 tx 선택해주기 -->
   </beans>
   ```
* mybatis-config.xml 설정
   * 파일위치 : src/main/resources/spring에 xml파일로 생성
   * DTO alias와 사용할 mapper 등록
      ```
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
         <settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- camelcase 설정 -->
         </settings>

         <typeAliases>
            <typeAlias type="sc.video.chat.dto.User" alias="user"/> <!-- DTO로 sc.video.chat.dto 패키지에 User라는 클래스를 생성 -->
         </typeAliases>

         <mappers>
            <mapper resource="mapper/usermapper.xml"/>  <!-- src/main/resources에 mapper라는 폴더를 생성하여 그 안에 xml파일 생성 -->
         </mappers>
      </configuration>
      ```
* *mapper.xml 설정
   * namespace 지정 및 sql구문 등록
      ```
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="sc.video.chat.usermapper">
         <insert id="insert" parameterType="user">
            insert into user(name, age) values (#{name}, #{age})
         </insert>
      </mapper>
      ```

# 개발환경 설정 (Class)
## servlet-context.xml 설정
* servlet-context로 지정 할 클래스파일 등록
   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <beans:beans xmlns="http://www.springframework.org/schema/mvc"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:beans="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
         http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

      <context:annotation-config></context:annotation-config>
      <beans:bean class="sc.video.chat.config.ServletContext"></beans:bean>

   </beans:beans>
   ```

* ServletContext 클래스 작성
   ```
   package sc.video.chat.config;

   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.ComponentScan;
   import org.springframework.context.annotation.Configuration;
   import org.springframework.web.servlet.ViewResolver;
   import org.springframework.web.servlet.config.annotation.EnableWebMvc;
   import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
   import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
   import org.springframework.web.servlet.view.InternalResourceViewResolver;

   @Configuration
   @EnableWebMvc
   @ComponentScan(basePackages = {"sc.video.chat.controller", "sc.video.chat.config"})
   public class ServletContext implements WebMvcConfigurer {

      @Override
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
         registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); // swagger 등록
         registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); // swagger 등록
         registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
      }

      @Bean
      public ViewResolver viewResolver() {
         InternalResourceViewResolver vr = new InternalResourceViewResolver();
         vr.setPrefix("/"); // Controller에서 jsp파일 위치를 src/main/webapp/ 로 판단
         vr.setSuffix(".jsp");
         return vr;
      }
   }
   ```
## root-context.xml 설정
* root-context로 지정 할 클래스파일 등록
   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

      <context:annotation-config></context:annotation-config>
      <bean class="sc.video.chat.config.RootContext"></bean>
   </beans>
   ```
* RootContext 클래스 작성
   ```
   package sc.video.chat.config;

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

   @Configuration
   @ComponentScan(basePackages = {"sc.video.chat.repository", "sc.video.chat.service"})
   public class RootContext {

   }
   ```

## web.xml 설정
* 위의 xml 방식과 동일

## MySQL, MyBatis 설정
* 참고사이트 : https://mybatis.org/mybatis-3/ko/index.html / http://mybatis.org/spring/ko/getting-started.html
* RootContext 클래스 변경
   ```
   package sc.video.chat.config;

   import org.apache.commons.dbcp2.BasicDataSource;
   import org.mybatis.spring.SqlSessionFactoryBean;
   import org.mybatis.spring.SqlSessionTemplate;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.ComponentScan;
   import org.springframework.context.annotation.Configuration;
   import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
   import org.springframework.jdbc.datasource.DataSourceTransactionManager;
   import org.springframework.transaction.PlatformTransactionManager;
   import org.springframework.transaction.annotation.EnableTransactionManagement;

   @Configuration
   @ComponentScan(basePackages = {"sc.video.chat.repository", "sc.video.chat.service"})
   @EnableTransactionManagement
   public class RootContext {

      @Bean
      public BasicDataSource dataSource() { // mysql연동
         BasicDataSource dataSource = new BasicDataSource();
         dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
         dataSource.setUrl("jdbc:mysql://localhost:3306/videochat?serverTimezone=UTC");
         dataSource.setUsername("root");
         dataSource.setPassword("<PASSWORD>");
         return dataSource;
      }

      @Bean
      public SqlSessionFactoryBean sqlSessionFactory() throws Exception { // mybatis 연동을 위한 SqlSessionFactory
         SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
         sqlSessionFactory.setDataSource(dataSource());
         sqlSessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml")); // mybatis-config 등록
         return sqlSessionFactory;
      }

      @Bean
      public SqlSessionTemplate sqlSession(SqlSessionFactoryBean sqlsessionFactory) throws Exception { // mybatis 연동을 위한 sqlSession
         return new SqlSessionTemplate(sqlsessionFactory.getObject());
      }

      @Bean
      public PlatformTransactionManager transactionManager() { // Transaction 처리를 위한 설정
         DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
         transactionManager.setDataSource(dataSource());
         return transactionManager;
      }
   }
   ```
* mybatis-config.xml 설정
   * 위의 xml 방식과 동일
* *mapper.xml 설정
   * 위의 xml 방식과 동일

## Swagger 설정
* ApplicationConfig, MVCConfig와 같은 패키지에 SwaggerConfig 클래스 생성
* Title, Description, Version을 정하여 클래스파일 작성
   ```
   package sc.video.chat.config;

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

   import springfox.documentation.builders.ApiInfoBuilder;
   import springfox.documentation.builders.PathSelectors;
   import springfox.documentation.builders.RequestHandlerSelectors;
   import springfox.documentation.service.ApiInfo;
   import springfox.documentation.spi.DocumentationType;
   import springfox.documentation.spring.web.plugins.Docket;
   import springfox.documentation.swagger2.annotations.EnableSwagger2;

   @Configuration
   @EnableSwagger2
   public class SwaggerConfig {

      private ApiInfo metadata() {
         return new ApiInfoBuilder().title("video-chat").description("we can communication in video").version("1.0").build();
      }

      @Bean
      public Docket api() {
      return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
               .paths(PathSelectors.any()).build().apiInfo(metadata());
      }
   }
   ```

# Mybatis log 설정
* src/main/resources/logback.xml에 코드 작성
   ```
   <!-- Application Loggers -->
   <logger name="${basepackage 이름}"> --> ex) <logger name="market.place">
      <level value="debug" />
   </logger>
   ```

# JWT 적용하기
## dependency 추가하기
* https://mvnrepository.com/ 에 접속하여 "JSON Web Token Support For The JVM" 선택 하여 dependency 추가
   ```
   <!-- JWT -->
   <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
   </dependency>
   ```
## 기본설정
* jwt 전용 패키지 만들기 ex) sc.video.chat.jwt
* 패키지 안에 JwtService 클래스 생성 및 작성
   ```
   package sc.video.chat.jwt;

   import java.util.Date;
   import java.util.Map;

   import org.springframework.stereotype.Component;

   import io.jsonwebtoken.Claims;
   import io.jsonwebtoken.Jws;
   import io.jsonwebtoken.Jwts;
   import io.jsonwebtoken.SignatureAlgorithm;
   import sc.video.chat.dto.User;

   @Component
   public class JwtService {
      private String secretKey = "video-chat"; // 암호화 알고리즘 적용 key
      private Long expireTime = 1000L * 60 * 5; // 토큰의 유효시간

      // 토큰 생성
      public String createUserToken(User user) {
         return Jwts.builder()
            .setHeaderParam("typ",  "JWT") // 토큰의 타입
               .setSubject("userToken") // 토큰의 제목
               .setExpiration(new Date(System.currentTimeMillis() + expireTime)) // 토큰의 유효시간
               .claim("user", user) // 토큰에 담고 싶은 정보
               .signWith(SignatureAlgorithm.HS256, secretKey.getBytes()) // secretKey를 사용하여 암호화 알고리즘 적용
               .compact(); // 직렬화 처리 (String으로 변경)
      }

      // 토큰을 사용하여 유저정보 얻기
      public Map<String, Object> getUser(String token) {
         Jws<Claims> claims = null;
         try {
            claims = Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token); // secretKey를 사용하여 복호화
         } catch(Exception e) {
            throw new RuntimeException();
         }

         return claims.getBody();
      }

      // 토큰의 유효성 검증
      // 문제가 존재하면 예외 발생, 그렇지 않은 경우는 문제 없다고 판단
      public void checkValid(String token) {
         Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token);
      }
   }
   ```
* 패키지 안에 JwtInterceptor 클래스 생성 및 작성
   ```
   package sc.video.chat.jwt;

   import javax.servlet.http.HttpServletRequest;
   import javax.servlet.http.HttpServletResponse;

   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.stereotype.Component;
   import org.springframework.web.servlet.HandlerInterceptor;

   @Component
   public class JwtInterceptor implements HandlerInterceptor {

      @Autowired
      private JwtService jwtService;

      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

         // option 요청은 바로 통과
         if(request.getMethod().equals("OPTIONS")) {
            return true;
         } else {
            String token = request.getHeader("jwt-user-token"); // request의 header에 jwt-user-token이라는 key값에 알맞는 value값을 token으로 저장
            if(token != null && token.length() > 0) {
               jwtService.checkValid(token); // 토큰의 유효성 검증
               return true;
            } else { // 해당 토큰의 유효성이 알맞지 않은 경우
               throw new RuntimeException("인증 토큰이 존재하지 않습니다.");
            }
         }
      }
   }
   ```
* ServletContext 클래스 수정 (basepackage 추가 및 Interceptor 등록)
   ```
   package sc.video.chat.config;

   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.ComponentScan;
   import org.springframework.context.annotation.Configuration;
   import org.springframework.web.servlet.ViewResolver;
   import org.springframework.web.servlet.config.annotation.CorsRegistry;
   import org.springframework.web.servlet.config.annotation.EnableWebMvc;
   import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
   import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
   import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
   import org.springframework.web.servlet.view.InternalResourceViewResolver;

   import sc.video.chat.jwt.JwtInterceptor;

   @Configuration
   @EnableWebMvc
   @ComponentScan(basePackages = {"sc.video.chat.controller", "sc.video.chat.config", "sc.video.chat.jwt"})
   public class ServletContext implements WebMvcConfigurer {

      @Override
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
         registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); // swagger 등록
         registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); // swagger 등록
         registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
      }

      @Bean
      public ViewResolver viewResolver() {
         InternalResourceViewResolver vr = new InternalResourceViewResolver();
         vr.setPrefix("/"); // Controller에서 jsp파일 위치를 src/main/webapp/ 로 판단
         vr.setSuffix(".jsp");
         return vr;
      }

      @Autowired
      private JwtInterceptor jwtInter;

      private final String excludePath[] = {
            // 토큰을 생성해야 하는 곳인 로그인과 회원가입 관련된 곳 제외, ex) http://localhost:8080/user/{*}
            "/user/**",

            // swagger 제외
            "/v2/api-docs",
            "/swagger-resources/**",
            "/swagger-ui.html/**",
            "/webjars/**"
      };

      @Override
      public void addCorsMappings(CorsRegistry registry) {
         registry.addMapping("/**")
               .allowedOrigins("*")
               .allowedMethods("*")
               .allowedHeaders("*")
               .exposedHeaders("jwt-user-token");
      }

      @Override
      public void addInterceptors(InterceptorRegistry registry) { // addCorsMappings와 addInterceptors의 위치가 바뀔 시 에러 발생 가능
         registry.addInterceptor(jwtInter)
               .addPathPatterns("/**") // 적용 경로 지정
               .excludePathPatterns(excludePath); // 제외 경로 지정
      }
   }
   ```
## Controller에 적용하기
* User 관련 Controller에 해당 Mapping 추가
   ```
   @Autowired
	JwtService jServ;

	@GetMapping("/user/selectByUserId")
	public ResponseEntity<Map<String, Object>> userSelectByUserId(@RequestParam String userId, @RequestParam String userPw, HttpServletResponse res) {
		try {
			User user = uServ.selectByUserId(userId);
			if(user.getUserPw().equals(userPw)) {
				String token = jServ.createUserToken(user); // user를 저장하는 토큰 생성
				res.setHeader("jwt-user-token", token); // response의 header에 jwt-user-token 이름으로 만들어진 토큰을 담아 client에 전달

				return response(user, HttpStatus.OK, true);
			} else {
				return response(0, HttpStatus.OK, false);
			}
		} catch(RuntimeException e) {
			return response(e.getMessage(), HttpStatus.CONFLICT, false);
		}
	}

	@GetMapping("/user/selectByUserToken")
	public ResponseEntity<Map<String, Object>> userSelectByUserToken(@RequestParam String userToken) {
		try {
			Map<String, Object> userTokenMap = jServ.getUser(userToken);
			return response(userTokenMap.get("user"), HttpStatus.OK, true);
		} catch(RuntimeException e) {
			return response(e.getMessage(), HttpStatus.CONFLICT, false);
		}
	}
   ```
## Client에 적용하기 (React에 적용)
* 로그인 axios에 localStorage 활용하기
   ```
   const onSignIn = () => {
        axios
            .get('/user/selectByUserId', {
                params: {
                    userId,
                    userPw,
                },
            })
            .then((res) => {
                if (res.data.data === 0) {
                    alert('로그인 실패');
                } else {
                    dispatch(setUserAction(res.data.data));
                    dispatch(setIsSignInAction());
                    localStorage.userToken = res.headers['jwt-user-token']; // jwt-user-token으로 response온 값을 localStorage에 저장
                    alert('로그인 성공');
                }
            })
            .catch((e) => {
                alert(e);
            });
    };
   ```
* App.js에서 token 존재여부 확인하여 자동 로그인
   ```
   const dispatch = useDispatch();

   const reduxIsSignIn = useSelector((state) => state.user.isSignIn);

   useEffect(() => {
      if (reduxIsSignIn === false && localStorage.userToken) {
         axios
            .get('/user/selectByUserToken', {
               params: {
                  userToken: localStorage.userToken,
               },
            })
            .then((res) => {
               dispatch(setUserAction(res.data.data));
               dispatch(setIsSignInAction());
            })
            .catch((e) => {
               alert(e);
            });
      }
   });
   ```
* excludePath에 해당하지 않은 axios 작성 방법
   ```
   axios
      .get('/notExcludePath', {
         headers: {
            "jwt-user-token": localStorage.userToken
         },
      })
   ```
## 갱신 토큰 만들기
* 토큰을 저장하는 DB 생성 (MySQL 기준)
   ```
   create table UserToken (
      user_id varchar(100) primary key, -- 사용자 아이디
      refresh_token varchar(500), -- 사용자 갱신 토큰
      updt_tm datetime -- 수정시간
   );
   ```
* 사용자 계정을 생성할 때 토큰 저장 DB에 데이터 같이 저장 (MyBatis 기준)
   ```
   <insert id="insertToken" parameterType="user">
		insert into UserToken(user_id, refresh_token, updt_tm)
		values (#{userId}, null, null)
	</insert>
   ```
* 일반 접근 토큰을 생성할 때 갱신 토큰도 생성하여 토큰 저장 DB 갱신 (User 관련 Controller 수정)
   ```
   ...
   if(user.getUserPw().equals(userPw)) {
      String token = jServ.createUserToken(user); // user를 저장하는 토큰 생성
      String refreshToken = jServ.createUserRefreshToken(user); // user를 저장하는 갱신 토큰 생성

      uServ.updateToken(new UserToken(userId, refreshToken, null)); // 갱신 토큰을 DB에 저장
      res.setHeader("jwt-user-token", token); // response의 header에 jwt-user-token 이름으로 만들어진 토큰을 담아 client에 전달

      return response(user, HttpStatus.OK, true);
   }
   ...
   ```
   ```
   <update id="updateToken" parameterType="userToken">
		update UserToken
		set refresh_token = #{refreshToken}, updt_tm = current_timestamp()
		where user_id = #{userId}
	</update>
   ```
* Client에 jwt-decode 패키지 설치 (React 기준)
   ```
   yarn add jwt-decode
   ```
* App.js에 내용 수정
   ```
   import JwtDecode from 'jwt-decode';
   ...
   useEffect(() => {
        if (reduxIsSignIn === false && localStorage.userToken) {
            axios
                .get('/user/selectByUserToken', {
                    params: {
                        userToken: localStorage.userToken,
                    },
                })
                .then((res) => {
                    dispatch(setUserAction(res.data.data));
                    dispatch(setIsSignInAction(true));
                })
                .catch(() => {
                    // 기존 접근 토큰에 예외 발생 시 갱신 토큰 확인
                    const userToken = JwtDecode(localStorage.userToken); // 기존의 Storage에 저장되어 있던 토큰 복호화
                    axios
                        .get('/user/selectByUserRefreshToken', {
                            params: {
                                userId: userToken.user.userId,
                            },
                        })
                        .then((res) => {
                            dispatch(setUserAction(res.data.data));
                            dispatch(setIsSignInAction(true));
                            localStorage.userToken = res.headers['jwt-user-token'];
                        })
                        .catch(() => {
                            localStorage.removeItem('userToken'); // 갱신 토큰도 만료되었을 시 재 로그인
                            alert('다시 로그인 해주세요');
                });
            });
        }
   });
   ...
   ```
   ```
   @GetMapping("/user/selectByUserRefreshToken")
	public ResponseEntity<Map<String, Object>> useSelectByUserRefreshToken(@RequestParam String userId, HttpServletResponse res) {
		try {
			UserToken userToken = uServ.selectTokenByUserId(userId);
			Map<String, Object> userTokenMap = jServ.getUser(userToken.getRefreshToken());

			User user = (User)userTokenMap.get("user");

			String token = jServ.createUserToken(user); // user를 저장하는 토큰 생성
			String refreshToken = jServ.createUserRefreshToken(user); // user를 저장하는 갱신 토큰 생성

			uServ.updateToken(new UserToken(userId, refreshToken, null)); // 갱신 토큰을 DB에 저장
			res.setHeader("jwt-user-token", token); // response의 header에 jwt-user-token 이름으로 만들어진 토큰을 담아 client에 전달

			return response(user, HttpStatus.OK, true);
		} catch(RuntimeException e) {
			return response(e.getMessage(), HttpStatus.CONFLICT, false);
		}
	}
   ```
   ```
   <select id="selectTokenByUserId" parameterType="string" resultType="usertoken">
		select *
		from UserToken
		where user_id = #{value}
	</select>
   ```<file_sep># DataStructure

## 해싱 (Hashing)
### 해싱이란 ?
* 키 값에 직접 산술적인 연산을 적용하여 항목이 저장되어 있는 테이블의 주소를 계산하여 항목에 접근하는 방식
* 자료를 저장할 때 배열을 사용하며 어떤 항목의 탐색 키만을 가지고 바로 항목이 저장되어 있는 배열의 인덱스를 결정하는 기법
* 키 값의 연산에 의해 직접 접근이 가능한 구조를 해시테이블(Hash Table)이라 부르고, 해시 테이블을 이용한 탐색을 해싱(Hashing)이라 한다.
* 해싱의 기본구조

* key 값을 가지고 해시함수에 접근하여 산술적인 연산을 거친 뒤 배열의 인덱스를 결정하는 hash값을 생성하여 value를 저장하는 방식
* 알고리즘 적으로 사용할 때는 중복된 값을 처리하는 알고리즘에서 해싱을 자주 사용

### 해싱의 문제점
* 해시 충돌(Hash Collision)이 발생한다
* key값을 해시함수를 통해 산술연산을 하여 hash값을 생성했지만 이전에 똑같은 hash값이 생성된 적이 있을 때 충돌이 발생
* n+1개의 비둘기(key값들)가 n개의 비둘기 집(hash값들)에 들어간다면 적어도 1개 이상의 비둘기 집에 2마리 이상의 비둘기가 있을 것이다 (비둘기 집의 원리)

### 해싱 충돌 (Hash Collision) 해결방법
* Seperate Chaining방식

   * 위의 사진을 보면 4번째의 <NAME>가 1번째의 <NAME>와 해시함수를 통해 생선된 해시값이 동일함
   * Chaining방식은 기존의 hash값에 등록된 John의 뒤에 Linked List방식을 사용하여 Sandra를 연결시키는 방식이다.
   * 장점
      * 한정된 저장소를 효율적으로 사용 가능
      * 해시 함수를 선택하는 중요성이 상대적으로 적다.
      * 상대적으로 적은 메모리를 사용한다. (미리 공간을 잡을 필요가 없기 떄문)
   * 단점
      * 한 Hash에 자료들이 계속 연결된다면 검색효율을 낮출 수 있음
      * 외부 저장공간을 사용한다.
      * 외부 저장공간 작업을 추가로 해야한다.
   * 시간복잡도
      * 삽입, 삭제, 검색: O(n)
* Open Addressing방식

   * 위의 사진을 보면 4번째의 <NAME>가 1번째의 <NAME>와 해시함수를 통해 생선된 해시값이 동일함
   * OpenAdressing방식은 기존의 hash값에 등록된 John이 존재할 때 그 다음 해시값에 Sandra를 저장하는 방식 (1개의 해시와 1개의 값이 매칭되어 있는 형태)
   * 5번째의 Ted같은 경우도 기존의 hash값에 Sandra가 등록되어 있기 때문에 그 다음 해시값에 Ted를 저장
   * 비어있는 해시를 찾는 과정은 항상 동일해야 한다.
   * 비어있는 해시 찾는 규칙
      * 선형탐색(Linear Probing): 다음 해시(+1)나 n개(+n)를 건너뛰어 비어있는 해시에 데이터를 저장
      * 제곱탐색(Quadratic Probing): 충돌이 일어난 해시의 제곱을 한 해시에 데이터를 저장
      * 이중해시(Double Hashing): 다른 해시함수를 한번 더 적용한 해시를 구해서 데이터를 저장
   * 장점
      * 또 다른 저장공간 없이 해시테이블 내에서 데이터 저장 및 처리가 가능
      * 또 다른 저장공간에서의 추가작업이 필요없다.
   * 단점
      * 해시함수의 성능에 전체 해시테이블의 성능이 결정된다.
      * 데이터의 길이가 늘어나면 그에 해당하는 저장소를 마련해야 된다 (1-1 방식이기 때문)
   * 시간복잡도
      * 삽입, 삭제, 검색: O(n)

languages:
Java
Markdown
JSON with Comments
JavaScript
TypeScript

repo_language: Java

repo_name: ksccmp/personal-study