https://www.acmicpc.net/problem/12865

 

12865번: 평범한 배낭

첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)가 주어진다. 입력으로 주어지는 모든 수는 정수이다.

www.acmicpc.net

 

Knapsack 알고리즘을 사용하면 풀리는 문제이다.

최근에 알고리즘 문제 풀이에 취미가 생겨 문제를 열심히 푸는 중인데, Knapsack 알고리즘을 봐도 이해가 안되어

나름 내 방식대로 이해하고 풀다 보니 Knapsack알고리즘과 똑같이 나왔다 ㄷㄷ.

 

주어진 개수 = n

주어진 무게 = weight

주어진 무게의 가치 = value

Knapsack 알고리즘은 전체 무게가 weight(주어진 무게)를 넘지 않도록 n 번째까지 항목 중에서 얻어진 최고 이익을

저장하여 Dynamic Programing으로 푸는 방식이다.

처음에 이게 무슨말인지 이해가 안되어 이해를 포기하고 그냥 내 생각대로 해보니 Knapsack알고리즘이 나왔는 데,

이 글 또한 그러한 사람들을 위해 작성 또는 내가 다시보려고 작성한 글이다.

 

문제에 나와있는 무게와 가치

6 13

4 8

3 6

5 12

말고

아래의 가치로 계산해보고 n(주어진 개수)=5 , WEIGHT(주어진 무게의 값)=15 라고 하자

weight value
1 1
2 2
3 3
4 4
5 5

 

5*15 로 2차원 배열을 잡는다. 실제로 생성할 땐 dp[n+1][value+1] 로 생성하자 dp[0][0~15]을 써야하기 때문.

또한. 값을 [1][1] 부터 집어넣자.

 

첫 번째 항목인

weight=1과 value=1을 먼저 계산한다.

 

weight=1과 주어진 무게의값 WEIGHT(15)까지 계산을 한다

계산은 각 열의 무게와 weight=1을 빼서 0보다 클경우 value를 집어 넣고

만약 작을 경우 그 이전의 값(바로 위의 행)을 집어넣는다. 그 이전의 값이란 것은 두번째 또는 세번째 항목부터 이해가 될 것이다.

 

현재 weight=1이고 첫 번째 열의 무게는 1이다 1-1>=0 이므로 1을 넣는다.

현재 weight=1이고 두 번째 열의 무게는 2이다 2-1>=0 이므로 1을 넣는다.

이런식으로 첫 번째 항목을 채운다

 

만약 첫 번째 항목의 weight=5라면 첫 번째 열에서는 1-5 >=0 이 아니므로 0을 넣는 데, 사실 0이라기보단 그 이전의 값(바로 위의 행)인 dp[0][1]값을 넣는 것이다. 두번째 또는 세번째 항목부터 이해가 될 것이다.

(행은 아이템 순번, 열은 무게)

두 번째 항목은 weight=2이고 value=2이다

 

현재 weight=2이고 첫 번째 열의 무게는 1이다 1-2>=0 이 아니다. 따라서 이 전의 값(바로 위의 행)을 넣어야한다.

이전의 값(바로 위의 행)은 dp[1][1]값인데 이는 즉, dp[n-1][j] 정도로 볼 수 있다.

 

현재 weight=2이고 두 번째 열의 무게는 2이다 2-2=>0 이므로 첫 번째 항목처럼 2를 넣는다라고 생각할 수 있지만

아니라 이전(바로 위의 행)의 값과 값을 비교해야한다.

이전(바로 위의 행)의 값은 1이고 현재 나의 값은 2이다 2가 더 크므로 2를넣는다.

 

현재 weight=2이고 세 번째 열의 무게는 3이다 3-2>=0 이고 3-2=1이다. 이제 남은 1의 값을 활용해야 한다.

이전(바로 위의 행)의 열을 사용한다. 

3-2=1 이기 때문에 이전 값(바로 위의 행)의 열은 1,1 -> dp[1][1]이다

현재 weight는 2이며, value=2이고 현재 열은 세 번째 열이다. 2, 3 -> dp[2][3]

현재 열은 이전 값(바로 위의 행)의 1번열의 값 + 현재 값 -> dp[2][3] = dp[1][1]+value -> dp[2][3]=1+2=3

dp[2][3]=3이란 것을 알 수 있다.

현재 weight=2일 때 세 번째 열의 무게가 3이라는 것을 알게 되었으면 이제 또 이전의 값(바로 위의 행)과 비교를 해야한다. 바로 위의행 dp[1][3]=1 이고 현재 dp[2][3]은 3이라는 것을 알고 3이 더크므로 3을 완전히 집어 넣는다.

 

이로써 규칙은

1. 먼저 그 열의 무게와 현재 항목의 무게를 빼고 무게가 0보다 큰지 확인한다. 

2. 0보다 작다면 이전의 값(바로 위의 행)값을 넣는다.

3. 0보다 크다면 현재 열의 무게와 현재 항목의 무게를 뺀 값을 더 한다.

더하는데 빼서 나온 값은 그 이전의 값(바로 위의 행에서 뺀 값의 열번째) 와 현재 항목의 가치를 더한다.

4. 값을 더했다면 이전의 값(바로 위의 행)의 값과 비교를 해 더 큰 값을 완전히 집어 넣는다.

 

이렇게 해서 두번째도 쭉 집어넣는다.

 

세 번째 항목은 weight=3이고 value=3이다

 

현재 weight=3이고 첫 번째 열의 무게는 1이다 1-3>=0 이 아니다. 따라서 이 전의 값(바로 위의 행)을 넣어야한다.

이전의 값(바로 위의 행)은 dp[2][1]값이다. 따라서 1을 넣는다

 

현재 weight=3이고 두 번째 열의 무게는 2이다 2-3>=0 이 아니다. 따라서 이 전의 값(바로 위의 행)을 넣어야한다.

이전의 값(바로 위의 행)은 dp[2][2]값이다. 따라서 2을 넣는다

 

현재 weight=3이고 세 번째 열의 무게는 3이다 3-3>=0 이다. 따라서 규칙대로 행동한다.

규칙의 3번인 0보다 크기 때문에 현재 열의 무게와 현재 항목의 무게를 뺀값을 더하는 데 3-3=0이다.

그리고 이 0의 값은 그 이전의 값( 바로 위의 행에서 뺀값의 열번째)는 dp[2][0]인데 이는 0이다.(dp[n+1][value+1] 로 잡았고 값을 [1][1]부터 잡았기때문에 0번쨰는 0으로 초기화 해준다.)

어쨋든 dp[2][0] + 현재 항목의 가치 -> dp[2][0]+3 -> 0+3 =3

그리고 규칙의 4번으로 와서 3과 그 이전의 값(바로 위의 행) dp[2][3]의 값과 비교해서 큰 값을 넣어준다. 둘이 값이 3으로 같기 때문에 3을 넣어준다.

 

현재 weight=4이고 네 번째 열의 무게는 4이다 4-3>=0 이다. 따라서 규칙대로 행동한다

규칙의 3번인 0보다 크기 때문에 현재 열의 무게와 현재 항목의 무게를 뺀값을 더하는 데 4-3=1이다.

그리고 이 1의 값은 그 이전의 값( 바로 위의 행에서 뺀값의 열번째)는 dp[2][1]인데 이는 1이다.

어쨋든 dp[2][1] + 현재 항목의 가치 -> dp[2][1]+3 -> 1+3 =4

그리고 규칙의 4번으로 와서 4와 그 이전의 값(바로 위의 행) dp[2][4]의 값과 비교해서 큰 값을 넣어준다. 

dp[2][4]=3이고 현재는 4이기 때문에 4가 더크므로 4를 넣어준다.

 

이런식으로 하다보면 아래의 값들이 나온다

 

 

이게 Knapsack알고리즘인데 이를 알고리즘적으로 풀이하면

dp[i][j] = Math.max(dp[i - 1][j], value[i] + dp[i - 1][j - weight[i]]);

이렇게 된다.

 

BOJ 12865 평범한 배낭 문제의 최종 소스는 아래와 같다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		String T;

		T = br.readLine();
		st = new StringTokenizer(T);

		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());

		int weight[] = new int[N + 1];
		int value[] = new int[K + 1];
		int dp[][] = new int[N + 1][K + 1];
		for (int i = 1; i < N + 1; i++) {
			T = br.readLine();
			st = new StringTokenizer(T);
			weight[i] = Integer.parseInt(st.nextToken());
			value[i] = Integer.parseInt(st.nextToken());
		}

		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= K; j++) {
				if (j - weight[i] >= 0) {
					dp[i][j] = Math.max(dp[i - 1][j], value[i] + dp[i - 1][j - weight[i]]);

				} else {
					dp[i][j] = dp[i - 1][j];
				}
			}
		}
		System.out.println(dp[N][K]);
	}
}


Node.js에서 google oauth 인증하는 방법이다. 

이전에는 passport를 사용하지 않는 방법을 이용하여 JWT와 함께 쿠키로 로그인 인증처리를 했다면,

이번엔 세션을 이용하여 passport를 이용하는 방법이다.


기본적으로 https://console.cloud.google.com 에서 프로젝트를 생성했다는 가정하에 작성한다.


1. passport를 이용하지 않은 google oauth 인증 후 jwt 사용

2019/01/07 - [Develop/Node.js] - [Node.js] google oauth 인증 (구글 로그인)




1. 적당한 프로젝트를 생성 후 아래와 같은 명령어를 입력한다.


npm init
npm install express --save
npm install express-session --save
npm install session-file-store --save
npm install passport --save
npm install passport-google-oauth


express 를 사용할 것이기 때문에 express를 설치한다. 그리고 passport는 기본적으로 session을 이용하기 때문에 express-session도 설치하고

session 저장소를 파일로 처리할 것이기 때문에 session-file-store 를 설치한다.

그리고 사용할 passport와 passport의 전략중 하나인 passport-google-oauth를 설치한다.


※아래엔 express-session에서 파일로 세션을 저장하는 것 말고 데이터베이스 등을 사용하는 방법을 확인 할 수 있다.

https://www.npmjs.com/package/express-session

2. express를 설정하고 다음 session을 설정한다.


먼저 server.js 파일을 생성하고 아래와 같이 작성한다.


https://www.npmjs.com/package/express-session 에 접속해서 기본적인express-session을 사용하기 위해

아래 주석 //1의 미들웨어를 설정한다.

주석 //2번은 localhost:3000으로 접속할 때 나오는 기본 화면이다.

주석 //3은 express를 통해  3000포트를 이용하여 서버를오픈한다.

var express = require('express');
var session = require('express-session')
var FileStore = require('session-file-store')(session)
var app = express();

app.use(session({                // 1
    secret: 'keyboardcat',
    resave: false, 
    saveUninitialized: true,
    store: new FileStore()
}))

app.get('/', function (req, res, next) {    //2
    console.log('홈')
    
})


app.listen(3000, function () {            //3
    console.log('Example app listening on port 3000!');
});


3.  Google Cloud Platform 에서 Json 파일을 다운받고, config 폴더를 만든 뒤 다운받은 json 파일을 config폴더에 넣는다.


사진 맨 오른쪽 하단에 빨간줄로 표시한 아이콘을 클릭하여 json파일을 다운받는다.


다운받은 json 파일엔 기본적으로 아래와같이 나와있다.



이 파일을 아래 처럼 넣는다. 이유는 그냥 가져다 쓰는 것보다 저렇게 가져오는 것이 조금이나마 안전하기 때문.



4. passport를 설정한다.


4-1. passport 전략 중 하나인 우리가 사용할 passport-google-oauth를 사용한다.


server.js에 추가한다.

//1 아래와 같이 passport를 가져오고

//2 passport를 초기화시킨다.

//3 session을 이용하기 때문에 추가한다.

//4 실질적으로 로그인할 수 있는 경로와 scope이다. scope에서는 email이나 기타 정보를 가져 올 수도 있다.

//5 메인페이지에 4번에서 설명한 실질적으로 로그인하는 경로를 설정한다.

var passport = require('passport'),
      GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;    //1

app.use(passport.initialize());    //2
app.use(passport.session());        //3

app.get('/auth/google',            //4
  passport.authenticate('google', { 
      scope: 'https://www.googleapis.com/auth/plus.login' }))

app.get('/', function (req, res, next)   {    //5
  
    var html = `
    login with google`
    res.send(html)
})


위와 같이 설정하고 'node server.js' 명령어를 통해 실행해서 접속하면 아래와 같은 화면이 뜬다.



4-2. 구글 로그인을 해서 정보를 얻어올 로직과 세션에 담을 로직을 구현한다.


//1 은 로그인했을 시 얻어오는 정보가아니라 그냥 임의로 넣었다 사실 이렇게 하는 것은 아니고 바로 아래에서 다시 설명할 예정

//2 로그인 성공하고 로그인 성공했을 때 가져오는 user.id를 세션에 설정한다.

//3 로그인이 성공했으면 페이지를 항상 새로 불러올 때마다 불러오는 것이다. 

그리고 세션에서 serialize에서 설정한 user id를 불러온 후 user 정보를 보여주는 것이다.

//4 아까 json파일에서 client_id와 secret, redirect uri를 불러온다.

//5 로그인이 성공했을 경우 scope를 설정한대로 구글측에서 profile 정보를 가져온다.


//5를 통해 로그인이 성공하면 scope에서 설정한 대로 profile에서 정보를 가져오는데 

성공적으로 가져왔으면 이를 done을 통해 정보를 설정하면

serializeUser로 가서 설정하고

설정된 값이 deserializeUser에서 불러온다.


다시말하자면 원래 var loginData를 설정하는 것이아니라

//5에서 일단 로그인 인증 처리를 한다 -> 데이터베이스에 넣거나, 데이터베이스에 있는 값을 가져와 회원이 맞는지 비교를 한다든지

이런 기본적인 로그인 처리를 설정하고. 이것이 통과된다면 done을 통해 정보를 return하면

//2의 serializeUser가 호출되는데 여기서 user.id ( 꼭 id가아니라 email이면 user.email도 가능) 을 session에 설정한다.

//3 그럼 페이지가 항상 호출될 때마다 deserializeUser가 호출 되는데 deserializeUser의 id를 통해

데이터베이스에 접속해서 이 아이디가 로그인이 된 상태라든지 이런 것들을 확인할 수 있다.

(너무 이상하게 설명한 것 같은데 이해가 되지않는다면 댓글에 적어주시면 감사하겠습니다.)


var config = require('./config/google.json')


var loginData = {        //1 id : 'test', pw : '1' } passport.serializeUser(function(user, done) {    //2 console.log('serialize', user); done(null, user.id); }); passport.deserializeUser(function(id, done) {    //3 console.log('deserialize', id); done(null, loginData) }); passport.use(new GoogleStrategy({                //4 clientID: config.web.client_id, clientSecret: config.web.client_secret, callbackURL: config.web.redirect_uris[0] }, function(accessToken, refreshToken, profile, done) {    //5 return done(null, loginData) } ));


4-3. 로그인이 정상 처리 되어 callback될 주소를 설정한다.


//1 Json파일의 redircet_uri를 설정한다 기본적으로 /auth/google/callback 이다.

//2 로그인이 정상 처리 된다면 //3의 redirect('/') 홈으로 가고 

실패하면 /bye로 보냈다. 이것은 다시 로그인 하는 곳으로 보낼 수 있고 처리하는 것은 개발자 마음이다.

//4 이것을 설정하지 않으면 세션이 스토어에 저장하는 데 오래걸릴 경우 리다이렉트를 시킬 수 있기 때문에 이를 방지하여

세션이 스토어에 저장이 완료되면 리다이렉트를 시키는 코드이다.

//5 로그인 실패시 보내는 주소이다.

//6 위에서 설정한 app.use(passport.session()) 미들웨어를 통해 req에서 user를 호출 할 수 있다. 

이것을 통해 deserilize가 호출 되는 것을 확인할 수 있다.


app.get('/auth/google/callback',     //1
passport.authenticate('google', {     //2
    failureRedirect: '/bye' 
    }),
    function(req, res) {                //3
        req.session.save(function(){     //4                              
          res.redirect('/');
        })
    });


app.get('/bye', function (req, res, next) {    //5
    res.send('login failed')
})

app.get('/', function (req, res, next) {
    console.log(req.user)        //6
    var html = `
    login with google`
    res.send(html)
})


5. 결과



node server.js 명령어를 통해 실행 후

login with google을 클릭하여 로그인을 하게되면

로그인이 성공하면 serialize를 호출하고 

페이지를 새로고침할 때마다 deserialize가 호출 되는 것을 볼 수 있다.



6. 전체 코드



var express = require('express');
var session = require('express-session')
var FileStore = require('session-file-store')(session)
var config = require('./config/google.json');
var app = express();

app.use(session({
    secret: 'keyboardcat',
    resave: false, 
    saveUninitialized: true,
    store: new FileStore()
}))

var loginData = {
    id : 'test',
    pw : '1'
}
var passport = require('passport'),
      GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;

app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser(function(user, done) {
    console.log('serialize', user);
    done(null, user.id);
  });
  
passport.deserializeUser(function(id, done) {
    console.log('deserialize', id);
    done(null, loginData)
  });


passport.use(new GoogleStrategy({
    clientID: config.web.client_id,
    clientSecret: config.web.client_secret,
    callbackURL: config.web.redirect_uris[0]
  },
  function(accessToken, refreshToken, profile, done) {
        return done(null, loginData)
    }
));

app.get('/auth/google',
  passport.authenticate('google', { 
      scope: 'https://www.googleapis.com/auth/plus.login' }))


app.get('/auth/google/callback', 
passport.authenticate('google', { 
    failureRedirect: '/bye' 
    }),
    function(req, res) {
        req.session.save(function(){    
                                       
            res.redirect('/');
        })
    });

app.get('/', function (req, res, next) {
    console.log(req.user)
    var html = `
    login with google`
    res.send(html)
})

app.get('/bye', function (req, res, next) {
    res.send('login failed')
})

app.listen(3000, function () {
    console.log('Example app listening on port 3000!');
});



참고 : 

https://www.npmjs.com/package/express-session 



Socket IO를 이용한 정말 간단한 채팅 프로그램이며, express를 사용한다.

기초만 쓴것이기 때문에 정말로 간단하다.


전체 코드 :  https://github.com/wonkwangyeon/Real_Simple_SocketIO_Chat


1. 프로젝트를 하나 만들고 아래의 명령어를 입력한다.


npm init
npm install express --save
npm install socket.io --save


2. 입력후 app.js라는 파일과 client.html라는 파일을 만든다.




3. app.js 파일에 들어가 아래와 같이 입력한다. (위 github에서 복사 가능)



5번째 라인 :  서버를 오픈한다. 또한 Socket.IO 홈페이지의 레퍼런스에서 app.listen으로 서버를 열지마라고 주의한다.

10번째 라인 : localhost:3000 으로 들어가면 바로 client.html을 열기 위함이다.

15번째 라인 : socket io 에 접속한다.

16번째 라인 : 클라이언트에서 들어오는 메세지를 받은후

18번째 라인 : 이 메세지를 접속한 모든 클라이언트에게 보낸다.


간단하다 on이 메세지를 받는 함수, emit이 메세지를 보내는 함수이다.  앞의 'receive'라는 것을 통해 어디로 보내고 어디로 받는지 정할 수 있다. 

클라이언트 부분을 보면 간단하게 이해가 된다.


4. client.html



20~32번째 라인 : 메세지를 받고 전송하는 간단한 입력 폼 전체 소스를 보면 20번째 위에는 아주 간단한 css가 설정되어있다.

33번째 라인 : 핵심. 이것을 통해 socket.io를 사용할 수 있다.

35번째 라인 : 서버에서 오픈한 socket 서버에 접속한다.

37번째 라인 : 버튼을 누르면 실행하는 이벤트이다.

38번째 라인 : 위에 설명하였듯이 emit이 메세지를 보내는 함수 이다. 그리고 'receive'라는 서버에서 설정한 이름을 통해 그곳으로 메세지를 보낸다.

39번째 라인 : 메세지를 보냈으면 현재 입력되어 있는 메세지를 초기화하는 것.

41번째 라인 : 위에 설명하였든이 on이 메세지를 받는 함수이다. 'client_receive'라는 이름이라고 설정하였고 서버에서

 emit을 'client_receive'라고 설정하여 보낸다.

42번째 라인 : 받은 내용을 22번째 라인의 textarea에 설정한다.


5. 결과


node app.js 를 통해 실행하고

localhost:3000로 접속한다.



기본적인 함수를 익히고 이를 통해 정말로 간단한 채팅프로그램을 만들어보았다.


전체 코드 :  https://github.com/wonkwangyeon/Real_Simple_SocketIO_Chat


참고 :  https://socket.io/docs/#What-Socket-IO-is

Spring boot에서 AWS Elastic Beanstalk 사용시 .ebextensions 사용하는 방법이다.

Elastic Beanstalk 사용시 배포방법이 war파일 배포방법과 jar파일 배포방법이 있는데

이 두개는 .ebextensions 사용시 기본적인 사용법은 같으나 배포시 차이점이 있다.



1. 2018/10/26 - [Develop/Spring Boot] - Spring Boot AWS Elastic BeanStalk을 이용한 배포

2. 2019/02/12 - [Develop/Spring Boot] - Spring boot ElasticBeanstalk 환경변수설정시 주의사항



1.  기본 사용방법으로는 .ebextensions 란 폴더를 프로젝트 최상위에 만든다. 




2. 만들었으면 이제 서버에 적용하고 싶은 값들을 .config 확장자로 만들어 .ebextensions의 폴더에 넣으면 된다.

기본적으로 적용하고싶은 파일들의 순서는 알파벳순서라서, 보통 00-이름.config, 01-이름2.config 이렇게 이름을 짓는다.


[Directory구조 예시]

Project

--.ebextensions 

-- 00-이름.config

-- 01-이름2.config


3. 이제 nginx설정하는 방법이다. (이것이 필요 없다면 바로 4번의 배포 방법으로 가시면 됩니다.)

아래의 사진처럼 폴더를 생성한다.


3-1 . 먼저 nginx 설정을 변경하고싶다면 nginx의 전체 속성을 일단 복사해서 가져온 뒤

nginx.conf라는 파일을 생성 후 복사한 전체 내용을 ngnix.conf에 붙여넣고 변경하고 싶은 내용을 추가하면 된다. 

위 사진처럼 nginx 폴더(conf.d폴더가아님) 바로 아래에 nginx.conf 파일을 넣는다. 이렇게 되면 nginx 의 전체설정을 서버에 완전히 쓰게 된다.


3-2. nginx 전체 설정 가져오기가 번거롭다면 위 사진처럼 conf.d 폴더와 그리고 아래에 elasticbeanstalk이라는 폴더를 생성한다.

conf.d폴더아래에 만약 .conf 파일을 생성한다면 이 내용은 nginx.conf 전체 속성의 http 단에 들어가고

elasticbeanstalk 폴더 아래에 만약 .conf 파일을 생성한다면 이 내용은 nginx.conf 전체 속성의 http 단의 sever 단에 들어간다


이게 무슨말이냐면 아래의 사진처럼 conf.d 폴더에 생성한 .conf 파일의 내용은 검정색 선 안에 들어가고

elasticbeanstalk 폴더 아래에 생성한 .conf 파일의 내용은 빨간색 선 안에 들어간다.



이런식으로 nginx 설정을 변경한다.


4. 배포는 war파일 배포와 jar파일 배포시 차이가 있다. 일단 공통적으로 zip 을 해주어야 한다.



4-1. war 파일은 .ebextensions 폴더와 함께 zip 파일로 만든후 이를 Elastic Beanstalk에 올리면 ebextensions 에서 설정한 값이 적용된다.



[.zip 파일 directory 예시]

.zip

-- .ebextensions 

-- .war


4-2. jar파일은 zip파일로 만들고 배포하면 설정이 먹히지 않는다. 그렇기 때문에 Procfile 이라는 것을 사용해야 한다.

Procfile을 말 그대로 Procfile이라고 프로젝트 최상위 경로에 생성한다 


[Directory구조 예시]

Project

--.ebextensions 

-- 00-이름.config

-- 01-이름2.config

--Procfile


생성 후 Procfile 내용안에 아래와 같이 작성한다.


web: java -jar jar파일경로/파일이름.jar


작성하였으면 이제 .ebextensions 폴더와 .jar파일 Procfile을 zip 파일로 압축한다.


[.zip 파일 directory 예시]

.zip

-- .ebextensions 

-- .jar

-- Procfile


그리고 이를 Elastic Beanstalk에 올리면 ebextensions 에서 설정한 값이 적용된다.



(어차피 zip파일로 압축할거면 굳이 root 폴더에 생성해야하는 이유가 무엇인지 궁금하다.

build 시 자동으로 zip 파일로 압축하는 방법이 있다고 하던데 알아봐야겠다.)



참고 :  https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/create_deploy_Java.html

  1. YBHwang 2019.11.13 03:21 신고

    jar로 배포하려는 1인입니다만, 저렇게하면 잘 배포되시나요??
    기본적으로 build/libs에 .jar파일이 추출되어서 /procfile와 /.ebextentions로 구성한 프로젝트를 배포해보면 항상 에러뜨더라구요ㅜ
    추가로 eb deploy로는 아예 배포가 안되구요... 너무 난감합니다ㅠㅠ

    • 팡연 2019.11.13 18:54 신고

      흠.. 네 저같은 경우는 이 글과 같이
      .zip 파일안에 Procfile파일과 .ebextensions폴더 000.jar 파일 이 3개 파일만 넣은채로 elastic beanstalk에 올렸을 경우 배포가 되었습니다.
      eb deploy가 되지 않는다면 aws elastic beanstalk 사이트에서 직접 한번 구성해서 배포를 한번 테스트 해보세요.
      도움이 된 것같진 않아 죄송합니다.


Spring boot ElasticBeanstalk 환경변수설정시 주의사항이다.

이것 때매 조금 고생을 한 적이 있다.


Spring boot에서 application.properties 작성시 대표적으로 server.port 등 이렇게 . (마침표)을 써서 사용한다

하지만 ElasticBeanstalk 에서는 아래의 사진처럼 이를 _ (언더스코어)로 구분하는데

Spring boot의  application.properties에는 - (하이픈)이 사용되는 것이 있다. 대충 이런것들 spring.aop.proxy-target-class이 있다.

이럴 경우 - (하이픈)은 . (마침표)가 아니라 그냥 두는 경향이 있는데 

이 또한 무조건 - (하이픈)을 _ (언더바)로 바꾼뒤 ElasticBeanstalk 의 환경변수설정에 입력해야 한다.

ex) SPRING_AOP_PROXY_TARGET_CLASS






리눅스에서 ssh key로 접속하는 방법이다. 

이 글은 현재 윈도우에서 ssh key가 생성되어있다는 가정하에 쓴 글이다.


1. 서버에서 .ssh 폴더가 있는지 확인한다. 

ls ~/.ssh

없다면 만들어 준다.

mkdir .ssh


2. 만들었으면 이제 현재 윈도우에서 가지고있는 public_key를 등록해주어야 한다.

등록하기 위해선 아래처럼 authorized_keys를 생성하여 등록한다.

cd .ssh
vim authorized_keys


3. 등록하였으면 이제 putty 또는 xshell로 접속할 때 ssh로 접근하면 자동으로 인식한다.


4. 이제 ssh키로만 접속할 수 있도록 비밀번호 접근은 못하게 막을 것이다.

관리자로 접속하여 아래의 명령어를 입력한다.


sudo -s
vim /etc/ssh/sshd_config


5. 아래 사진에 나와있는 PasswordAuthentication 을 찾아 no로 변경한다.

처음에는 주석처리로 되어있고 yes로 되어있을 것이다.




6. 이제 이렇게 하면 패스워드로 접근할 경우 아래의 사진처럼 비활성화가 되며 자동으로 ssh로만 접근할 수 있게 한다.(xhell의 경우)




어플리케이션 개발에 무엇을 이용할지 고민하다 Flutter를 선택하게되었다. React Native도 아주 조금하다가. 결국 손을 떼버렸다......

선택이유는 그냥 Flutter를 해보고 싶었다. 


ReactNative 설치

1. 2018/11/11 - [Develop/React Native] - React Native 설치 Windows, Android Studio 사용하기.

2. 2018/11/11 - [Develop/React Native] - React Native expo를 이용한 설치 Windows, Android Studio (v31.0.0)



이 글은 

안드로이드 스튜디오로 하는 방법과 VSCODE로 Flutter 설치 및 실행하는 방법이다.


1.  https://flutter.io/docs/get-started/install/windows 이 주소로 들어가 SDK zip를 다운받는다.

2. flutter_console


2-1. 다운받았으면 위에 나와있는 것 처럼 C:\src\flutter 경로에 압축을 푼다. 위 설명에보면 C:\Program Fils에 하지 않는 이유가 권한을 요청하기 때문이라고 한다.

아래와 같이 압축을 풀었으면 위 설명대로 flutter_console.bat를 실행한다.




2-2. flutter_console에 늘 이렇게 폴더에 들어가서 bat를 클릭해서 실행하기 귀찮다 그러므로 path를 등록하여 간단하게 바로 cmd창을 통해 실행을 하자.

아래의 설명처럼 flutter\bin의 경로를 Path에 지정해주면된다.



내 PC 우측 클릭 -> 고급시스템설정 -> 환경변수 -> Path에 아래와 같이 추가한다.




이렇게 추가하였으면 이제 간단하게 cmd창을 열고 flutter_console을 실행할 수 있다.


3. cmd창 또는 flutter_console.bat를 통해 flutter이용에 필요한 리스트를 점검해보자.



콘솔창에 flutter doctor 라고 입력하면 아래와 같이 리스트가 나온다.

현재 안드로이드 스튜디오가 깔려있지 않기 때문에 [X]라고 나오고 설치되지 않았다고 나온다

필자는 현재 VS Code가 깔려있어서 저렇게 나온다.

그리고 device가연결된게 없다고 나온다.




4. https://developer.android.com/studio/ 옆의 경로로 들어가서 안드로이드 스튜디오를 설치한다. 


파일을 다운받고 Next를 누르고 일단 설치한다. (설치하는 것 자체는 어렵지 않음)


설치하였으면 이제 flutter를 추가해주어야 한다. 아래의 사진처럼 plugin에 들어간다



5. 접속하였으면 flutter라고 입력한다.

아래의 사진처럼 flutter라고 입력하면 Search in repositories라고 나오는 데 이것을 클릭하고

flutter를 Install한다. 

Install 하였으면 안드로이드 스튜디오를 재시작한다.




6. 안드로이드 스튜디오를 재시작 하였으면 아래의 사진처럼 Start a new Flutter project가 생성된다.

이것을 클릭한 후 프로젝트를 하나 생성한다.




7. 프로젝트를 생성하였으면 가상 DEVICE를 설치한다.

아래의 사진들 처럼 Tools -> AVD Manager -> Create Virtual Device 를 통해 AVD를 하나 생성한다
필자는 Nexus 5x를 생성하였다.




8. flutter doctor를 통해 점검해본다.

이제 안드로이드 스튜디오가 설치 되어있는 것을 확인할 수 있다.

그리고 To resolve this, run : flutter doctor --andorid-licenses 를 볼 수 있는데

말그대로 flutter doctor --andorid-licenses 를 cmd 창에 입력한 후 모두 yes하면 된다.



9. AVD를 실행한 뒤 flutter doctor를 실행한다.


AVD를 실행하면 아래와 같이 모두 완료되었다고 알려준다.




10. 마지막으로 run -> main.dart를 통해 실행해본다.




11. 이제 VSCODE로 한번 실행해보자

https://code.visualstudio.com/ 에 접속하여 VSCODE를 다운받는다.


12. 다운받고 실행하였으면 좌측 메뉴 맨 밑에 탭을 클릭하고 DART를 설치한다.




13. 설치하였다면

맨위 탭에 View -> Command Palette (ctrl + shift + p) 를 클릭 또는 단축키를 이용한 뒤

Flutter: New Project를 클릭하여 적절한 폴더에 프로젝트를 생성한다.



14. 안드로이드 스튜디오의 AVD매니저를 실행하고 Debug -> Start Without Debugging 을 통해 실행한 다음 확인한다.




이로써 안드로이드 스튜디오에서 flutter 작동하는 방법 및 VSCODE에서 flutter 작동하는 방법을 알아 보았다.


'Develop > Flutter' 카테고리의 다른 글

Flutter 설치 Windows(Android Studio, VSCODE)  (0) 2019.01.28



LocalDateTime 의 형식 때문에 Gson으로 JSON으로 변경시 아래의 사진 처럼 Data가 Deserialize 하게 나온다.



처음엔 Gson 때문인지 몰랐고 jackson-datatype-jsr310 이거 때문인 줄 알아, 정말 쌩고생을 했음에도 불구하고 안되다가

혹시나 해서 LocalDateTime 형식 때문에 Json으로 변경시 이상하게 되는가 싶어 확인했다가 정말로 Gson때문인 걸 알게되었다.

이유는 아마도 LocalDateTime 형식에서 2019-01-06T00:24:38 가운데 T 때문에 Gson에서 자동으로 위의 사진처럼 date와 time으로

나누어 주는 것 같다.



1. JPA를 사용할 것이고, DB의 날짜 형식은 TimeStamp를 사용한다. 일단 Converter를 만들어야 한다. Spring boot에서의 JPA는 2.1을 사용하기 때문에 JPA 2.1은 Java8에서의 LocalDateTime을 지원하지 않기 때문에 Converter를 만든다. 


Converter를 만드는 법은 아래와 같이 어노테이션을 추가하거나


@SpringBootApplication
@EntityScan(basePackageClasses = {Application.class, Jsr310JpaConverters.class} )
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}


아래와 같이 직접 만든다.


import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

@Converter(autoApply = true)
public class DateConverter implements AttributeConverter<LocalDateTime, Timestamp> {
	@Override
	public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
		return (locDateTime == null ? null : Timestamp.valueOf(locDateTime));
	}

	@Override
	public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
		
		return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
	}
}


2. 만들었으면 데이터베이스를 통해 Date를 가져오는 것은 문제 없이 작동된다.

하지만 값을 찍어보면 Deserialize 하게 나온다. Gson을 사용하지 않고 순수하게 값만 가져올 경우는 이제

pom.xml에 아래의 maven을 추가한 뒤


       

			com.fasterxml.jackson.datatype
			jackson-datatype-jsr310
 

application.properties에 아래처럼 추가하면 Deserialize 하게 나오던 값이 serialize 나온다.

spring.jackson.serialization.write-dates-as-timestamps=false

하지만 우리가 할 것은 Gson을 통해 변경하는 것이기 때문에 위와 같이 하여도 아무런 변화를 느끼지 못할 것이다.


3. 이제 Gson을 통해 변경하기 위해 클래스를 생성한다.


import java.lang.reflect.Type;
import java.time.LocalDateTime;

import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class GsonConfig implements JsonSerializer<localdatetime>{

	@Override
	public JsonElement serialize(LocalDateTime date, Type type, JsonSerializationContext json) {
		// TODO Auto-generated method stub
		return new JsonPrimitive(date.toString());
	}

}


그리고 이제 Gson을 사용시 아래처럼 사용한다.


Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new GsonConfig()).create();
String jsonString = gson.toJson("변경할 값");

4. 이렇게 하면 2019-01-06T00:24:38 가운데 T 때문에 맨위의 사진처럼  Deserialize 나온 값이

 아래의 사진처럼  Serialize하게 나온다.



Node.js에서 google oauth 인증하는 방법이다. 

passport를 사용하는 방법이 있지만 passport는 사용해보니 무조건 session을 이용해야 했다.(아닐 수도 있음.)

이 글은 passport를 사용하지 않는 방법이다.


1.passport를 사용한 google oauth 인증

2019/03/11 - [Develop/Node.js] - [Node.js] google oatuh passport




프로젝트는 아래의 글을 사용했다. (순수 express 설치하는 글임)

2019/01/06 - [Develop/Node.js] - [Node.js] Express 설치(Windows)


1. googleapis 를 설치한다

npm install googleapis --save

2. google cloud console에서 .json파일을 받는다.


받은 파일을 아래와 같이 config 폴더에 넣어 google.json으로 변경한다.




3. server.js 파일에 아래와 같이 추가한다.


install 한 googleapis를 불러오고 .json 파일에서 client_id와 secret, redirect_uri를 불러온다.

const { google } = require('googleapis');
var googleClient = require('./config/google.json');

const googleConfig = {
  clientId: googleClient.web.client_id,
  clientSecret: googleClient.web.client_secret,
  redirect: googleClient.web.redirect_uris[0]
};

사용할 scopes를 입력하고, OAuth2.0에 실질적으로 연결할 정보들을 다시 연결한다. 

scope는 아래 API라이브러리에서 본인이 사용하고 싶은 api를 보고 가져오면된다.

여기서는 google api + 를 이용할 예정이다.



const scopes = [
  'https://www.googleapis.com/auth/plus.me'
];


const oauth2Client = new google.auth.OAuth2(
  googleConfig.clientId,
  googleConfig.clientSecret,
  googleConfig.redirect
);

로그인 url을 받아오기 위한 정보를 입력한다. 

access_type에는 online과 offline이 있는데 offline으로 해야 제일 처음 로그인 했을 때 refresh_token을 받아온다.

(refresh_token은 항상 제일 처음 로그인할 때 가져온다 그러므로 다시 가져오고 싶으면 https://myaccount.google.com/permissions 에서 액세스된 어플리케이션을 해제해야 한다.)


scope는 위에 입력한 scope들을 가져온다. 지금처럼 scope하나만 가져올경우 

scope : 'https://www.googleapis.com/auth/plus.me' 바로 적어도 된다. 하지만 여러 scope를 가져올 경우 위에 처럼 배열을 하나 만들어

여러 scope들을 사용할 수 있다.

그리고 google+ api를 사용하기 위해 google+ api에 대한 정보를 입력한다.

const url = oauth2Client.generateAuthUrl({
  
  access_type: 'offline',

  
  scope: scopes
});

function getGooglePlusApi(auth) {
  return google.plus({ version: 'v1', auth });
}

실질적으로 로그인해서 정보를 불러올 코드를 작성한다. 

간단하게 리프레시토큰, 액세스토큰, displayName과 id를 얻어와본다.

async function googleLogin(code) {
  const { tokens } = await oauth2Client.getToken(code);
  oauth2Client.setCredentials(tokens);
  oauth2Client.on('tokens', (token) => {
    if(tokens.refresh_token){
      console.log("리프레시 토큰 :", tokens.refresh_token);
    }
    console.log("액세스 토큰:", tokens.access_token);
  });
  const plus = getGooglePlusApi(oauth2Client);
  const res = await plus.people.get({ userId: 'me' });
  console.log(`Hello ${res.data.displayName}! ${res.data.id}`);
  return res.data.displayName;
}

4. login할 주소와 로그인이 되었을 경우 실행될 callback 주소를 작성한다.

로그인할 주소는 아까 로그인 할 url 받아올 url변수의 정보를 redirect 시키고

callback 주소는 google.json의 redirect 주소를 적으면 된다.

그리고 로그인이 성공했을 경우 돌아올 redircet주소를 적는다. 


app.get('/login', function (req, res) {
  res.redirect(url);
});

app.get("/auth/google/callback", async function (req, res) {

  const displayName = await googleLogin(req.query.code);
  console.log(displayName);

  res.redirect("http://localhost:3000");
});

5. 실행한다.



localhost:3000/login 으로 들어간다.




localhost:3000/login 으로 들어가면 아까 url 을 통해 아래의 사진으로 연결된다.


이제 아이디를 쳐서 로그인하면 아까 입력한 scope에 따라 권한을 받게되고 이후 성공하면 redirect 시킨 home으로 돌아온다.



그리고 console을 확인해보면 현재 액세스토큰과 displayName, id가 나왔다.

리프레시토큰은 전에 로그인을 한번해서(시험 삼아 한번 해봄). 안나왔지만, 제일 처음 로그인을 했다면 리프레시토큰 또한 나올 것이다.

(리프레시토큰은 제일 처음 한번만 발급된다.)



6. 전체코드



var express = require('express');
var app = express();

const { google } = require('googleapis');
var googleClient = require('./config/google.json');

const googleConfig = {
  clientId: googleClient.web.client_id,
  clientSecret: googleClient.web.client_secret,
  redirect: googleClient.web.redirect_uris[0]
};

const scopes = [
  'https://www.googleapis.com/auth/plus.me'
];

const oauth2Client = new google.auth.OAuth2(
  googleConfig.clientId,
  googleConfig.clientSecret,
  googleConfig.redirect
);

const url = oauth2Client.generateAuthUrl({

  access_type: 'offline',

  scope: scopes
});

function getGooglePlusApi(auth) {
  return google.plus({ version: 'v1', auth });
}

async function googleLogin(code) {
  const { tokens } = await oauth2Client.getToken(code);
  oauth2Client.setCredentials(tokens);
  oauth2Client.on('tokens', (tokens) => {
    if(tokens.refresh_token){
      console.log("리프레시 토큰 :", tokens.refresh_token);
    }
    console.log("액세스 토큰:", tokens.access_token);
  });
  const plus = getGooglePlusApi(oauth2Client);
  const res = await plus.people.get({ userId: 'me' });
  console.log(`Hello ${res.data.displayName}! ${res.data.id}`);
  return res.data.displayName;
}

app.get('/login', function (req, res) {
  res.redirect(url);
});

app.get("/auth/google/callback", async function (req, res) {

  const displayName = await googleLogin(req.query.code);
  console.log(displayName);

  res.redirect("http://localhost:3000");
});



app.get('/', function (req, res) {
  res.send('Hello World!');
  console.log("로그인 해서 홈으로 돌아옴");

});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});




참고 : 


https://www.npmjs.com/package/googleapis




Node.js에서 Jwt token을 생성하고 검증하는 방법이다. Jwt토큰을 이용하면 사용자인증을 처리할 수 있으므로 주로 로그인 인증방식에 사용한다.


먼저 프로젝트 생성은 아래글의 프로젝트를 사용하였다.(내용은 길지 않음.)

2019/01/06 - [Develop/Node.js] - [Node.js] Express 설치(Windows)



1. Node.js에서 jwt를 사용하기 위해 jsonwebtoken을 설치한다


npm install jsonwebtoken --save


2. 설치하였으면 프로젝트에 require("jsonwebtoken")을 추가한다.




3. jwt.sgin()을 이용하여 생성.



sign() 이라는 함수를 이용하여 jwt를 생성한다.

Private clain에 key가 test, value가 test인 데이터를 입력하였고

Signature 자리엔 secretKey라는 데이터를 입력하였다.

Public Claim 자리엔 subject(토큰제목)와 expiresIn(만료시간), issuer(발급자) 데이터를 입력하였다.


sign() 함수의 default 알고리즘으로 HMAC SHA256 알고리즘을 사용한다.




4. token 을 불러와서 검증해본다.



위의 터미널에서 생성된 토큰(eyJh 로 시작)을 복사한 후 https://jwt.io/ 들어가서 아래와 같이 검증해본다.


먼저 키 입력자리에 jwt 생성한키 (secretKey) 를 입력한다.



입력 후 복사한 token을 붙여넣으면 Signature Verified를 볼 수 있으며,

PayLoad 자리에 iat(생성시간), exp(만료시간), iss(발급자), sub(제목)를 볼 수 있다.



5. 이제 nodejs에서 verify라는 함수를 사용해 검증해본다.



jwt.verify( 발급받은 token, 생성할때 사용한 비밀키, 옵션(없어도 됨) )을 입력하여  검증하고

검증된다면 PayLoad의 Private Claim 값을 불러올 수 있다. 



검증에 성공된다면 아래와 같이 위에서 입력한 Private Claim 값인 test를 불러 올 수 있다.



만약 비밀키가 틀리거나, 만료시간이 지날경우 catch단에서 에러를 잡고 에러의 이유를 출력해준다.


※ 비밀키는 주로 config 폴더를 만들어서 config.json 파일을 따로 생성하여 그 곳에 집어넣고 생성과 검증할 때 그곳에서 비밀키를 불러와서 사용한다.

git을 사용할 경우 gitignore를 통해 config.json파일을 ignore 시켜준다.


아래의 레퍼런스 사이트에 접속하면 함수의 옵션들과 에러가 날 경우 어떤 에러가 발생하는지 확인할 수 있다.

참고 : https://github.com/auth0/node-jsonwebtoken


+ Recent posts