Riverpod는 기존의 provider 를 개선해서 나온 업그레이드 버전이다. 

정식 패키지는 

https://riverpod.dev/ko/

 

Riverpod

안전하게 Provider 읽기 Provider를 읽는 중 더 이상 bad state가 되지 않습니다. 만약 Provider를 읽기 위한 필요한 코드를 작성하면, 당신은 유효한 값을 얻을 수 있습니다. Provider는 비동기적으로 로드된

riverpod.dev

자세한 설명은

https://velog.io/@yeahsilver/Flutter-Riverpod-Riverpod%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-Riverpod%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

 

Flutter: Riverpod - Riverpod란 무엇인가?, Riverpod을 사용하는 방법

: 경랑화된 상태관리 라이브러리런타임이 아닌 컴파일 시간에 프로그램임 오류를 감지어플리케이션 상태를 쉽게 관리하고 액세스하는 동시에 상태 불변성 (state immutabilty), 상태 격리 (state isolati

velog.io

 

아래는 새로 프로젝트를 생성시 나오는 카운터앱을 간단하게 riverpod 방식으로 바꾼것이다. 

눈여겨 봐야 할건

1. StateProvider를 통해 동적 상태관리
2. ref.read(appCount.notifier).state++ 를 통해서 상태변화 푸시
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

final appCount = StateProvider<int>((ref) {
  return 1;
});



class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    int count = ref.watch(appCount);
    return Scaffold(
      appBar: AppBar(

        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text("Riverpod Sample app"),
      ),
      body: Center(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$count',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          ref.read(appCount.notifier).state++;
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

'스터디 > Flutter' 카테고리의 다른 글

Dart 에서 Promise.all 을 구현해보자  (0) 2020.02.29
Flutter + Node + docker Sample Code  (0) 2019.06.14
Future of Flutter by Google IO  (0) 2019.05.22
커스텀 뷰 공부 1일차  (0) 2019.03.20
Flutter + Sqflite + Stetho 사용하기  (0) 2019.01.04

안녕하세요. 수지아빠입니다. 

현재 블로그내 글을 안적은지 몇개월이 되어가네요. 그래서 이번참에 글을 다시 적어볼려고 생각합니다. 

30일이상 매일 한개의 다양한 주제의 글을 올려보는게 현재로서는 목표입니다.

1. 개발

2. 부동산 공부

3. 인생 공부


그리고 현재 Flutter 단톡방도 운영중입니다.

개발관련해서 궁금하시거나 토론하실 내용 있으시면 여기 오셔서 말씀해주세요. 

https://open.kakao.com/o/gsshoXJ

 

Flutter 개발자 모임

#flutter #android #ios #안드로이드 #아이폰 #모바일 #선물요정소환

open.kakao.com

 

안녕하세요. 수지아빠입니다. 

오늘은 마스크를 사기위해 온라인에서 며칠간 구매를 시도했었지만 쉽지는 않아서 알람을 받을수 있는 앱을 개발하게 되었습니다. 

https://play.google.com/store/apps/details?id=kr.pe.mask.alarm

 

마스크 실시간 알람 - Google Play 앱

국내 판매중인 마스크 판매 실시간 알람을 보내주는 앱입니다. 기능 1. 판매 시간에 대한 카운터 다운 2. 현재 구매 버튼이 활성화시 실시간 알람 서비스 개발예정중인 기능 1. 구매 시간 앞 알람을 설정 2. 다른 카테고리 추가(손소독제등) 모쪼록 필요하신 분들은 유용하게 쓰시기 바랍니다. ---- 개발자 연락처 : 07043562312

play.google.com

그럼 기능들 설명입니다. 

현재 약 1000원내외로 싸고 팔고 있는 리스트들을 보여주고 있습니다. 

판매 실시간 알람 서비스

일정한 시간마다 계속 사이트를 실시간 체크 하고 있으며 사이트에 구매버튼이 활성화 되는 순간 알람을 받으실 수 있습니다. 추가로 판매 시간 10분전에는 미리 알람을 받아서 준비를 할 수 있습니다.

현재로서는 이 기능이 대표적이지만 앞으로 더 많은 기능을 업데이트 할 예정입니다. 감사합니다.

https://play.google.com/store/apps/details?id=kr.pe.mask.alarm

 

마스크 실시간 알람 - Google Play 앱

국내 판매중인 마스크 판매 실시간 알람을 보내주는 앱입니다. 기능 1. 판매 시간에 대한 카운터 다운 2. 현재 구매 버튼이 활성화시 실시간 알람 서비스 개발예정중인 기능 1. 구매 시간 앞 알람을 설정 2. 다른 카테고리 추가(손소독제등) 모쪼록 필요하신 분들은 유용하게 쓰시기 바랍니다. ---- 개발자 연락처 : 07043562312

play.google.com

 

Flutter 대표 카카오톡 개발자 톡을 운영중입니다.

https://open.kakao.com/o/gsshoXJ

 

Flutter 개발자 모임

#flutter #android #ios #안드로이드 #아이폰 #모바일 #선물요정소환

open.kakao.com


자바스크립트에서 많이 사용되는 Promise.all 을 다트에선 어떻게 구하는지 알아볼 예정입니다.

Promise.all 은 여러개의 Promise 를 모아서 한꺼번에 처리해주는 역할을 해준다. 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

 

Promise.all()

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부합니다.

developer.mozilla.org

그럼 다트에서는 어떻게 사용되는 지 살펴보겠다. 

void main() {
  final f1 = getValue(delay : 1, value: 1);
  final f2 = getValue(delay : 5, value: 2);
  final f3 = getValue(delay : 2, value: 3);
  final f4 = getValue(delay : 3, value: 4);
  
  Future.wait([f1,f2,f3,f4])
     .then((value) => print(value));
  
}

Future<int> getValue({int delay, int value}) => Future.delayed(Duration(seconds: delay), () => Future.value(value));

 문제는 저건 순서대로 진행될텐데 병렬로 할려면 어떻게 하는지 궁금 하신 분은(Isolate사용) 아래 링크에서 보시길 바랍니다.

https://buildflutter.com/flutter-threading-isolates-future-async-and-await/

 

Flutter Threading: Isolates, Future, Async And Await - Build Flutter

Flutter applications start with a single execution process to manage executing code. Inside this process you will find different ways that the process handles multiple pieces of code executing at the same time. Isolates When Dart starts, there will be one

buildflutter.com

'스터디 > Flutter' 카테고리의 다른 글

Flutter + Riverpod 카운터앱  (0) 2023.12.11
Flutter + Node + docker Sample Code  (0) 2019.06.14
Future of Flutter by Google IO  (0) 2019.05.22
커스텀 뷰 공부 1일차  (0) 2019.03.20
Flutter + Sqflite + Stetho 사용하기  (0) 2019.01.04

이 내용은 https://www.udemy.com/course/vue-js-course 를 공부해서 정리한 내용입니다.

개발완료 화면은 다음과 같습니다.

Input을 넣으면 텍스트에 맞게끔 이미지가 생성됩니다. 

vue는 선언형 형태로 개발되며 다음 세가지 flow를 가진다고 한다. 

  • Data : 데이터 초기화
  • Compute : 주어진 데이터를 조합해서 리턴해줍니다.
  • Method : 만들어진 데이터를 이용해서 랜더링해서 화면을 다시 보여줍니다. 

Html

<div id="app">
  <h1>My identicon generator</h1>
  <div>
    Input:
    <Input v-on:input="onInput" />    
  </div>  
  <div>
    Output:
    <div v-html="identicon"></div>
  </div>  
</div>  

Javascript

new Vue({
  el: '#app',
  data: {
    textInput: ''
  },
  computed: {
    identicon: function() {
      return jdenticon.toSvg(this.textInput, 200);
    }
  },
  methods: {
    onInput: function(event) {
      this.textInput = event.target.value;
    }
  }
})

이상입니다.

저장소 정리하다 보니 1월에 진행된 간단한 백엔드 연동된 스터디 자료가 있어서 공유합니다. 

내용 : 간단한 투두 리스트

구현내용
1. Flutter
2. Bloc
3. RxDart
4. Docker
5. Node, Express
6. nginx proxy

https://github.com/bear2u/flutter-ecommerce-study

'스터디 > Flutter' 카테고리의 다른 글

Flutter + Riverpod 카운터앱  (0) 2023.12.11
Dart 에서 Promise.all 을 구현해보자  (0) 2020.02.29
Future of Flutter by Google IO  (0) 2019.05.22
커스텀 뷰 공부 1일차  (0) 2019.03.20
Flutter + Sqflite + Stetho 사용하기  (0) 2019.01.04
 .final__clip-image-1 {
     -webkit-clip-path: circle(50% at 50% 50%);
             clip-path: circle(50% at 50% 50%);
   }


   .final__clip-image-2 {
     -webkit-clip-path: polygon(0% 0%,
                                100% 0%,
                                100% 75%,
                                75% 75%,
                                75% 100%,
                                50% 75%,
                                0% 75%);
             clip-path: polygon(0% 0%,
                                100% 0%,
                                100% 75%,
                                75% 75%,
                                75% 100%,
                                50% 75%,
                                0% 75%);
   }


   .final__clip-image-3 {
     -webkit-clip-path: polygon(20% 0%,
                                0% 20%,
                                30% 50%,
                                0% 80%,
                                20% 100%,
                                50% 70%,
                                80% 100%,
                                100% 80%,
                                70% 50%,
                                100% 20%,
                                80% 0%,
                                50% 30%);
             clip-path: polygon(20% 0%,
                                0% 20%,
                                30% 50%,
                                0% 80%,
                                20% 100%,
                                50% 70%,
                                80% 100%,
                                100% 80%,
                                70% 50%,
                                100% 20%,
                                80% 0%,
                                50% 30%);
   }


   .final__clip-image-4 {
     -webkit-clip-path: polygon(0% 20%,
                                40% 20%,
                                40% 0%,
                                100% 50%,
                                40% 100%,
                                40% 80%,
                                0% 80%);
             clip-path: polygon(0% 20%,
                                40% 20%,
                                40% 0%,
                                100% 50%,
                                40% 100%,
                                40% 80%,
                                0% 80%);
   }

.shadow-typo {
        position: relative;
        display: inline-block;
        font-size: 4em;
        text-transform: uppercase;
        color: #00b3b4;
        text-shadow: 3px 3px 0px #e7eef1, 8px 8px 0px rgba(0, 0, 0, 0.1);
   }

   .threedee-typo {
        font-size: 4em;
        text-transform: uppercase;
        color: #f2395a;
        -webkit-transform: skew(-5deg, -5deg) rotate(5deg);
                transform: skew(-5deg, -5deg) rotate(5deg);
        -webkit-transform-origin: center center;
                transform-origin: center center;
        text-shadow: 1px 1px #d10e31,
                    2px 2px #d10e31,
                    3px 3px #d10e31,
                    4px 4px #d10e31,
                    5px 5px #d10e31,
                    6px 6px #d10e31,
                    7px 7px #d10e31,
                    8px 8px #890920,
                    9px 9px #890920,
                    10px 10px #890920,
                    11px 11px 15px rgba(0,0,0,0.5);
  }

Day2

Sexy Typograping

 

'스터디 > WEB' 카테고리의 다른 글

Clipping CSS - Day3  (0) 2019.05.22
Css next grid 클론 ( 1 ~ 5 )  (0) 2018.11.17
PostCSS 스터디 정리 ( 2주차 )  (0) 2018.11.12
Web 디자인시 유의 할 점 정리  (0) 2018.11.09
CSS Next 정리 ( 부산 1주차 HTML + CSS 스터디 )  (0) 2018.11.07

React Styled-Component

Basic

  • styled-components 을 사용해서 스타일링 할 수 있다.
  • this -> & 로 내부에서 사용가능
  • props 도 사용 가능하다.
import React, { Component, Fragment } from 'react';
import styled from 'styled-components';


class App extends Component {
  render() {
    return (
      <Container>
        <Button>Success</Button>
        <Button danger>Danger</Button>
      </Container>
    );
  }
}

const Container = styled.div`
  height: 100vh;
  width: 100%;
  background-color: #bdc3c7;
`

const Button = styled.button`
  border-radius: 50px;
  padding: 5px;
  min-width: 120px;
  color: white;
  font-weight: 600;
  -webkit-appearance: none;
  cursor: pointer;
  &:active,
  &:focus {
    outline: none;
  }
  background-color: ${props => props.danger ? "#e74c3c" : "#3498db"}
`

export default App;

GlobalStyle 적용시

import React, { Component, Fragment } from 'react';
import styled, { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    margin: 100px;
    padding: 100px;
    background-color: red;
  }
`

class App extends Component {
  render() {
    return (      
        <Container>
          <GlobalStyle />
          <Button>Success</Button>
          <Button danger>Danger</Button>
        </Container>           
    );
  }
}

const Container = styled.div`
  height: 300px;
  width: 300px;  
  background-color: blue;
`

const Button = styled.button`
  border-radius: 50px;
  padding: 5px;
  min-width: 120px;
  color: white;
  font-weight: 600;
  -webkit-appearance: none;
  cursor: pointer;
  &:active,
  &:focus {
    outline: none;
  }
  background-color: ${props => props.danger ? "#e74c3c" : "#3498db"}
`

export default App;

extenstion

기존 컴포넌트를 확장해서 새로운 태그를 만들어낼 수 있다.

const Anchor = Button.withComponent("a");

<Anchor href="http://naver.com">Go to Naver</Anchor>

추가적으로 더 확장을 할 수 있다.

이전버전에서는 .extend 로 가능했지만 새로 바뀐 내용은 styled 로 묶어줘야 한다.

// a tag 밑줄 제거

const Anchor = styled(Button.withComponent("a"))`
  text-decoration:none;
`;

animation

  • css, keyframes 추가
  • keyframe
  • props에 duration 설정 후 styled-components 에서 속성으로 사용가능
import React, { Component, Fragment } from 'react';
import styled, { createGlobalStyle, css, keyframes } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    margin: 100px;
    padding: 100px;
    background-color: red;
  }
`

class App extends Component {
  render() {
    return (      
        <Container>
          <GlobalStyle />
          <Button>Success</Button>
          <Button danger duration={1}>Danger</Button>
          <Anchor href="http://naver.com">Go to Naver</Anchor>
        </Container>           
    );
  }
}

const Container = styled.div`
  height: 300px;
  width: 700px;  
  background-color: blue;
`

const Button = styled.button`
  border-radius: 50px;
  padding: 5px;
  min-width: 120px;
  color: white;
  font-weight: 600;
  -webkit-appearance: none;
  cursor: pointer;
  &:active,
  &:focus {
    outline: none;
  }
  background-color: ${props => props.danger ? "#e74c3c" : "#3498db"}
  ${props => {
    if(props.danger) {
      return css`animation: ${rotation} ${props.duration}s linear infinite`
    }
  }}
`

const Anchor = styled(Button.withComponent("a"))`
  text-decoration:none;
`;

const rotation = keyframes`
  from{
    transform: rotate(0deg);
  }
  to{
    transform: rotate(360deg);
  }
`

export default App;

Theme

attrs

const Input = styled.input.attrs({
  required: true
})`
  border-radius: 5px;
`

class App extends Component {
  render() {
    return (      
        <Container>
          <GlobalStyle />          
          <Input placeholder="hello"/>
        </Container>           
    );
  }
}

css

컴포넌트에 css를 추가적으로 적용하고 싶을때

const awesomeCard = css`
  background-color: white;
  border-radius: 10px;
  padding: 20px;
`

const Input = styled.input.attrs({
  required: true
})`
  border-radius: 5px;  
  ${awesomeCard}
`

Global Theme

  • 전체 APP 내 공통 색상을 정의를 해서 props로 사용이 가능하다.
  1. ThemeProvider 를 import 한다.
  2. theme.js 를 적용한다.
  3. Component에 props 로 추가한다.
    1. background-color: ${props => props.theme.successColor}
import React, { Component } from 'react';
import styled, { createGlobalStyle, ThemeProvider } from 'styled-components';
import theme from './theme';

const GlobalStyle = createGlobalStyle`
  body {
    margin: 0px;
    padding: 0px;    
  }
`;


class App extends Component {
  render() {
    return (      
      <ThemeProvider theme={theme}>
        <Container>
          <GlobalStyle />          
          <Form />
        </Container> 
      </ThemeProvider>          
    );
  }
}

const Container = styled.div`
  height: 100%;
  width: 100vh;    
`

const Button = styled.button`
  border-radius: 30px;
  padding: 25px 15px;
  background-color: ${props => props.theme.successColor}
`

const Card = styled.div`
  background-color:white;
`

const Form = () => (
  <Card><Button>Hello</Button></Card>
)

export default App;

Nesting

부모 컴포넌트에서 자식 컴포넌트 속성 체크를 할 수 있다.

const Container = styled.div`
  height: 100%;
  width: 100vh;    
  background-color: yellow;
  ${Card}{
    background-color: blue;
  }
`

이상으로 Styled-Components 기본 스터디 내용이었다.

Gitflow 공부

init

git flow init

소스트리를 사용해서 진행해볼 예정이다.

오른쪽 상단에

1553063706869

를 클릭해서 branch등을 설정해준다.

설정화면에서 시작하기를 눌러서 진행해준다.

그리고 각각의 기능별로 시작시 오른쪽 상단 깃 플로우를 클릭해주면

1553063778711

feature(새기능)

1553064053079

그런다음 소스를 수정해서 다시 finish를 할 수 있다.

1553064090783

1553064103429

이런식으로 finish 를 하게 되면 develop 브랜치에 자동으로 merge 되는 걸 볼수 있다. 만약 체크를 하면 rebase 가 된다.

release

출시 버전을 관리를 하는데 출력을 보면

Summary of actions:
- A new branch 'release/1.0' was created, based on 'develop'


- You are now on branch 'release/1.0'

     

release branch를 만들는 걸 볼수 있다.

finish 를 진행 시 태그를 남길수 있도록 해준다.

1553064482136

1553064551643

보시다시피 태그가 달린 걸 볼수 있다.

핫픽스

핫픽스는 master에서 급한 수정을 넣을때 사용할 수 있다.

1553064657912

태그를 같이 넣어서 수정이 가능하다.

이상으로 git-flow를 알아보았다.

참고 사이트


Flutter 화면 제작 공부

https://www.youtube.com/watch?v=dMLreUXpSQ0&t=1s

를 보고 하나씩 공부 하고자 한다.

우선 사이즈 부분을 알아보자.

1553055691923

  • Row를 사용해서 정렬
  • Container Boxdecoration을 이용해서 박스형태 제작
  • 그림자 효과 적용

소스

높이 자동 계산 로직

double baseHeight = 640.0;

double screenAwareSize(double size, BuildContext context) {
  return size * MediaQuery.of(context).size.height / baseHeight;
}
import 'package:flutter/material.dart';
import 'package:flutter_adidas_shoes_exam/utils.dart';
import 'data.dart';

void main() => runApp(MaterialApp(
      home: MyApp(),
      debugShowCheckedModeBanner: false,
    ));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

List<String> sizeNumlist = [
  "7",
  "8",
  "9",
  "10",
];

class _MyAppState extends State<MyApp> {
  int _currentSizeIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Padding(
        padding: const EdgeInsets.all(30.0),
        child: Column(
          children: <Widget>[
            Row(
              children: sizeNumlist.map((item) {
                var index = sizeNumlist.indexOf(item);
                return GestureDetector(
                    child: sizeItem(item, _currentSizeIndex == index, context),
                    onTap: (){
                      setState(() {
                        _currentSizeIndex = index;
                      });
                    },
                );
              }).toList(),
            )
          ],
        ),
      ),
    );
  }
}

Widget sizeItem(String item, bool isSelected, BuildContext context) {
  return Padding(
    padding: EdgeInsets.only(left: 8.0),
    child: Container(
      width: screenAwareSize(30.0, context),
      height: screenAwareSize(30.0, context),
      decoration: BoxDecoration(
        color: isSelected ? Color(0xFFFC3930) : Color(0xFF525663),
        borderRadius: BorderRadius.circular(5.0),
        boxShadow: [
          BoxShadow(
            color: isSelected ? Colors.black.withOpacity(.5) : Colors.black12,
            offset: Offset(0.0, 10.0),
            blurRadius: 10.0
          )
        ]
      ),
      child: Center(
        child: Text(
          item,
          style: TextStyle(color: Colors.white, fontFamily: "Montserrat-Bold", fontSize: 11.0),
        ),
      ),
    ),
  );
}

Clipper를 이용한 뷰

1553060095143

위와 같이 구성할려면 알아야 할 내용

  • Clip
  • BoxShadow
  • BoxDecoration
class MClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
      //1  
    path.lineTo(0.0, size.height);
      //2  
    path.lineTo(size.width * 0.5, size.height);
      //3
    path.lineTo(size.width, size.height * 0.2);
      //4
    path.lineTo(size.width, 0.0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}

Path는 펜슬로 위치를 잡아가면서 그려주는 역할을 해준다.

1553060539050

그림이 상당히 이상하지만 순서대로 따라가면서 이렇게 그려주면서 다각형(?)을 그려준다.

전체 소스

import 'package:flutter/material.dart';
import 'package:flutter_adidas_shoes_exam/utils.dart';
import 'data.dart';

void main() => runApp(MaterialApp(
      home: MyApp(),
      debugShowCheckedModeBanner: false,
    ));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  int _currentColorIndex = 0;

  List<Widget> colorSelector() {
    List<Widget> colorItemList = new List();
    for (var i = 0; i < colors.length; i++) {
      colorItemList
          .add(colorItem(
          colors[i],
          i == _currentColorIndex,
          context,
              () {
            setState(() {
              _currentColorIndex = i;
            });
          }
      ));
    }

    return colorItemList;
  }

  Widget colorItem(Color color, bool isSelected, BuildContext context,
      VoidCallback _ontab) {
    return Padding(
      padding: EdgeInsets.all(16.0),
      child: Container(
        width: screenAwareSize(30.0, context),
        height: screenAwareSize(30.0, context),
        decoration: BoxDecoration(
            color: Colors.black,
            borderRadius: BorderRadius.circular(5.0),
            boxShadow: [
              BoxShadow(
                  color: Colors.black.withOpacity(.8),
                  blurRadius: 10.0,
                  offset: Offset(0.0, 10.0)
              )
            ]
        ),
        child: ClipPath(
          clipper: MClipper(),
          child: Container(
            width: double.infinity,
            height: double.infinity,
            color: color,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        width: double.infinity,
        margin: EdgeInsets.only(left: screenAwareSize(20.0, context)),
        child: Row(
          children: colorSelector(),
        ),
      ),
    );
  }

}

class MClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    path.lineTo(0.0, size.height);
    path.lineTo(size.width * 0.5, size.height);
    path.lineTo(size.width, size.height * 0.2);
    path.lineTo(size.width, 0.0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}

다음에는 프로그래스바를 이용해서 커스텀 뷰를 공부해볼 예정이다.

할 줄 아는 것과 해 보고 싶은 것

최근 풀스택(잡부라고도 한다)으로 개발하다 보니 과연 내가 무엇을 할 줄 알고 미래에는 무엇을 하고 싶은지 생각을 할 때가 있다. 그래서 이번 참에 한번 정리를 해보고자 한다.

할 줄 아는 것

  1. 자바 (12년)

  2. Android (10년)

  3. JSP + 톰캣 (3년)

  4. Spring (5년)

  5. IOS (1년)

  6. Flutter (6개월)

  7. Golang (2개월)

  8. PHP (7년)

  9. Mysql (12년)

  10. Oracle (1년, 11g 자격증 보유)

  11. Mssql (1년)

  12. sqlite (10년)

  13. mongodb (6개월)

  14. node (1년)

  15. javascript (4년)

  16. es6 (6개월)

  17. Graphql (입문)

  18. docker (3개월)

  19. kubernetes (입문)

  20. Tensorflow (입문)

  21. RxJava (2년)

  22. Kotlin (2년)

  23. AWS (2년)

  24. DAPP (1년)

  25. Ethereum (6개월)

  26. EOS (4개월)

  27. Hyperledger (입문)

  28. Python (입문)

  29. Android Things (2년, 중급)

  30. React (1년)

  31. Swift (RxSwift만 가능)

  32. Html5 (입문)

  33. CSS (입문)

  34. Linux (입문)

  35. Git (입문)

  36. Svn (중급)

  37. nginx (입문)

  38. 계속 추가중

해보고 싶은 거나 좀 더 잘하고 싶은 것들

  1. Flutter

  2. Tensorflow python

  3. Tensorflow JS

  4. Python 시각화

  5. Html + Css

  6. Vue

  7. React 중급

  8. 알고리즘

  9. EOS 심화과정

  10. nginx 심화과정

  11. kubernetes


부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

도커 & 쿠버네티스 9주차 스터디 

pod 자세한 설명 스크립트

# kubectl describe <object type> <object name>

> kubectl describe pod client-pod

...........

Name:         client-pod
Namespace:    default
Node:         minikube/10.0.2.15
Start Time:   Sat, 02 Feb 2019 12:05:16 +0900
Labels:       component=web
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"component":"web"},"name":"client-pod","namespace":"default"},"spec":{"container...
Status:       Running
IP:           172.17.0.16
Containers:
  client:
    Container ID:   docker://465ecbe522f537a36c26c021d88c1efb21782daf1e6fffd1e93be3469701a4d5
    Image:          bear2u/multi-worker
    Image ID:       docker-pullable://bear2u/multi-worker@sha256:6559ad68144e14b8f6f3054ab0f19056853ea07a7c4ead068d9140bd0a33b926
    Port:           3000/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 09 Feb 2019 10:24:04 +0900
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Sat, 09 Feb 2019 10:06:12 +0900
      Finished:     Sat, 09 Feb 2019 10:24:01 +0900
    Ready:          True
    Restart Count:  3
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-28mbg (ro)
Conditions:
  Type           Status
  Initialized    True
  Ready          True
  PodScheduled   True
Volumes:
  default-token-28mbg:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-28mbg
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason                 Age               From               Message
  ----    ------                 ----              ----               -------
  Normal  Scheduled              6d                default-scheduler  Successfully assigned client-pod to minikube
  Normal  SuccessfulMountVolume  6d                kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-28mbg"
  Normal  Pulling                6d                kubelet, minikube  pulling image "bear2u/multi-client"
  Normal  Pulled                 6d                kubelet, minikube  Successfully pulled image "bear2u/multi-client"
  Normal  Created                6d                kubelet, minikube  Created container
  Normal  Started                6d                kubelet, minikube  Started container
  Normal  SuccessfulMountVolume  12h               kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-28mbg"
  Normal  SandboxChanged         12h               kubelet, minikube  Pod sandbox changed, it will be killed andre-created.
  Normal  Pulling                12h               kubelet, minikube  pulling image "bear2u/multi-client"
  Normal  Pulled                 12h               kubelet, minikube  Successfully pulled image "bear2u/multi-client"
  Normal  Created                12h               kubelet, minikube  Created container
  Normal  Started                12h               kubelet, minikube  Started container
  Normal  SuccessfulMountVolume  25m               kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-28mbg"
  Normal  SandboxChanged         25m               kubelet, minikube  Pod sandbox changed, it will be killed andre-created.
  Normal  Pulling                25m               kubelet, minikube  pulling image "bear2u/multi-client"
  Normal  Pulled                 25m               kubelet, minikube  Successfully pulled image "bear2u/multi-client"
  Normal  Killing                7m                kubelet, minikube  Killing container with id docker://client:Container spec hash changed (3635549375 vs 3145631940).. Container will be killed and recreated.
  Normal  Pulling                7m                kubelet, minikube  pulling image "bear2u/multi-worker"
  Normal  Created                7m (x2 over 25m)  kubelet, minikube  Created container
  Normal  Pulled                 7m                kubelet, minikube  Successfully pulled image "bear2u/multi-worker"
  Normal  Started                7m (x2 over 25m)  kubelet, minikube  Started container

업데이트 오류

만약 pod 설정파일에서 containerPort를 변경시 어떻게 되는지 보자

> kubectl apply -f client-pod.yaml

.......

the Pod "client-pod" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)
{"Volumes":[{"Name":"default-token-28mbg","HostPath":null,"EmptyDir":null,"GCEPersistentDisk":null,"AWSElasticBlockStore":null,"GitRepo":null,"Secret":{"SecretName":"default-token-28mbg","Items":null,"DefaultMode":420,"Optional":null},"NFS":null,"ISCSI":null,"Glusterfs":null,"PersistentVolumeClaim":null,"RBD":null,"Quobyte":null,"FlexVolume":null,"Cinder":null,"CephFS":null,"Flocker":null,"DownwardAPI":null,"FC":null,"AzureFile":null,"ConfigMap":null,"VsphereVolume":null,"AzureDisk":null,"PhotonPersistentDisk":null,"Projected":null,"PortworxVolume":null,"ScaleIO":null,"StorageOS":null}],"InitContainers":null,"Containers":[{"Name":"client","Image":"bear2u/multi-worker","Command":null,"Args":null,"WorkingDir":"","Ports":[{"Name":"","HostPort":0,"ContainerPort":

A: 9999,"Protocol":"TCP","HostIP":""}],"EnvFrom":null,"Env":null,"Resources":{"Limits":null,"Requests":null},"VolumeMounts":[{"Name":"default-token-28mbg","ReadOnly":true,"MountPath":"/var/run/secrets/kubernetes.io/serviceaccount","SubPath":"","MountPropagation":null}],"VolumeDevices":null,"LivenessProbe":null,"ReadinessProbe":null,"Lifecycle":null,"TerminationMessagePath":"/dev/termination-log","TerminationMessagePolicy":"File","ImagePullPolicy":"Always","SecurityContext":null,"Stdin":false,"StdinOnce":false,"TTY":false}],"RestartPolicy":"Always","TerminationGracePeriodSeconds":30,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"default","AutomountServiceAccountToken":null,"NodeName":"minikube","SecurityContext":{"HostNetwork":false,"HostPID":false,"HostIPC":false,"ShareProcessNamespace":null,"SELinuxOptions":null,"RunAsUser":null,"RunAsGroup":null,"RunAsNonRoot":null,"SupplementalGroups":null,"FSGroup":null},"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"default-scheduler","Tolerations":[{"Key":"node.kubernetes.io/not-ready","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300},{"Key":"node.kubernetes.io/unreachable","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300}],"HostAliases":null,"PriorityClassName":"","Priority":null,"DNSConfig":null}

B: 3000,"Protocol":"TCP","HostIP":""}],"EnvFrom":null,"Env":null,"Resources":{"Limits":null,"Requests":null},"VolumeMounts":[{"Name":"default-token-28mbg","ReadOnly":true,"MountPath":"/var/run/secrets/kubernetes.io/serviceaccount","SubPath":"","MountPropagation":null}],"VolumeDevices":null,"LivenessProbe":null,"ReadinessProbe":null,"Lifecycle":null,"TerminationMessagePath":"/dev/termination-log","TerminationMessagePolicy":"File","ImagePullPolicy":"Always","SecurityContext":null,"Stdin":false,"StdinOnce":false,"TTY":false}],"RestartPolicy":"Always","TerminationGracePeriodSeconds":30,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"default","AutomountServiceAccountToken":null,"NodeName":"minikube","SecurityContext":{"HostNetwork":false,"HostPID":false,"HostIPC":false,"ShareProcessNamespace":null,"SELinuxOptions":null,"RunAsUser":null,"RunAsGroup":null,"RunAsNonRoot":null,"SupplementalGroups":null,"FSGroup":null},"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"default-scheduler","Tolerations":[{"Key":"node.kubernetes.io/not-ready","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300},{"Key":"node.kubernetes.io/unreachable","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300}],"HostAliases":null,"PriorityClassName":"","Priority":null,"DNSConfig":null}

image-20190209104247175

  • 이미지만 변경할수 있다는 걸 유념하자

Deployment

기존 포드형태에서는 이미지말고는 변경이 안된다. 이걸 극복하기 위해서 Depoyment라는 개념을 하나 더 추가를 하자

Deployment에서는 Pod 설정을 가지고 있다.

pod 에서 포트를 변경시 Deployment 에서는 포트를 죽이고 새로운 포트를 올린다.

그럼 새 파일을 만들어서 설정 파일을 만들자

client-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-deployment
spec:
  replicas: 1
  selector: 
    matchLabels:
      component: web
  template:
    metadata:
      labels:
        component: web
    spec:
      containers:         
        - name: client
          image: bear2u/multi-client
          ports:
            - containerPort: 3000 
  • template 밑에 포드를 구성
  • replicas 를 1개이상 설정시 템플릿을 여러개 만든다

image-20190209113138607

설정 파일 삭제

기존 설정 파일 삭제를 할 때 사용되는 스크립트

kubectl delete -f client-pod.yaml

10초 정도 후에 확인을 해보자

kubectl get pods

...
no resources found

새로운 deployment 설정 파일 적용

$ kubectl apply -f client-deployment.yaml
deployment.apps "client-deployment" configured
  • pods -> deployments 로 명령어가 변경이 되는 점 유의
$ kubectl get deployments
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
client-deployment     1         1         1            1           56d
  • pods 확인시 deployment 로 자동 생성되는 걸 확인 가능하다
$ kubectl get pods
NAME                                  READY     STATUS    RESTARTS   AGE
client-deployment-848b54d879-ch26z    1/1       Running   5          56d
  • 이미지를 바꿔서 새롭게 deployment 에서 포드가 변경되는 걸 보자
$ kubectl get pods
NAME                                  READY     STATUS              RESTARTS   AGE
client-deployment-848b54d879-ch26z    1/1       Running             5          56d
client-deployment-89bb69575-54pnn     0/1       ContainerCreating   0          5s
$ kubectl get pods
NAME                                  READY     STATUS    RESTARTS   AGE
client-deployment-89bb69575-54pnn     1/1       Running   0          43s
  • 자세한 설명을 보여주는 명령어
$ kubectl get pods -o wide
NAME                                  READY     STATUS    RESTARTS   AGE       IP            NODE
client-deployment-89bb69575-54pnn     1/1       Running   0          7m        172.17.0.1   minikube

image-20190209120048329

kubectl describe pods client-deployment
  • deployment.yaml 에서 replica를 변경시 숫자를 주목하자
...
replicas: 5
....
$ kubectl apply -f client-deployment.yaml

$ kubectl get deployment
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
client-deployment     5         3         3            1           56d
  • 하나씩 하나씩 올라가는 걸 확인 할 수 있다.

도커 업데이트 이미지 배포

만약 도커내 이미지가 업데이트 되는 경우 배포를 하는 경우에 대해서 알아보자

  • 도커 허브에 푸시
  • 마지막 이미지를 다시 가져와서 배포
kubectl apply -f client-deployment.yaml

nothing changed

하지만 이미 변경된 내용이 없기 때문에 적용이 안된다. 이 부분을 해결하기 위해서는 다음 3가지의 방법이 있을 수 있다

  • 수동으로 설정을 삭제한다 (비추천)
  • 태그별로 이미지를 만들어서 업데이트 진행(많이 쓰는 편)
    • 이미지가 변경되기 때문에 새로운 포드가 올라가고 업데이트를 진행한다
    • ex) bear2u/multi-client:v1...v2
  • 선언형 명령어로 업데이트 선언

새로운 업데이트 단계

  1. 도커 이미지 태그별로 생성

    docker build -t bear2u/multi-client:v2 .
    
  2. 도커 허브로 푸시

    docker push <tag>
    
  3. 쿠버네티스 업데이트 명령어로 실행

    # kubectl set image <object_type> / <object_name> <container_name> = <new image to use>
    
    $ kubectl set image deployment/client-deployment client=bear2u/multi-client:v2
    
  4. 업데이트 서버 테스트

    http://192.168.99.100:31515
    

image-20190209130349946

image-20190209131039743

  • 현재 로컬에 있는 도커와 쿠버네티스(미니큐브)는 다른 가상서버라서 도커가 분리되서 공유되지 않는다

해당 미니큐브 서버로 접속하기 위해서는 eval 명령어를 이용할 수 있다. (도커 환경 설정이 가능하다)

실행시 미니큐브에 있는 도커 콘테이너를 볼수 있다

$ eval $(minikube docker-env)
$ docker ps
........
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS PORTS                                                                NAMES
5551a5172d45        bear2u/multi-client          "nginx -g 'daemon of…"   14 minutes ago      Up 14 minutes                                                                      k8s_client_client-deployment-747d8c754-kncgq_default_17678d42-2c1f-11e9-8634-08002761bac3_0
5d2e22a0c7c9        k8s.gcr.io/pause-amd64:3.1   "/pause"                 14 minutes ago      Up 14 minutes                                                                      k8s_POD_client-deployment-747d8c754-kncgq_d
$ minikube docker-env

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/bear2u/.minikube/certs"
export DOCKER_API_VERSION="1.35"
# Run this command to configure your shell:
# eval $(minikube docker-env)

그럼 이 과정이 필요한 이유는 ?

  • 직접 쿠버네티스 노드 안에 도커 로그를 확인할 경우

  • 쿠버네티스내 캐싱된 도커들 정리할 경우

    • docker system prune -a
      

kubectl 로 minikube 도커로 접속가능

kubectl exec -it <docker image name>
kubectl logs <docker image name>

결론

오늘 공부한 내용은 다음과 같다.

  • 포드에 대한 설명 및 실행 스크립트
  • 업데이트시 하는 방법에 대해서 고민
  • Pods와 Depolyment와의 관계
  • minikube 내 도커 시스템 접속 및 관리
  • 설정 파일 삭제 방법

이상으로 9주차 도커 & 쿠버네티스 스터디 정리 내용이다. 참석해주셔서 감사합니다.

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

Kubernetes

minikube start

상태확인

minikube status

클러스터 정보 확인

kubectl cluster-info

목표

다양한 도커 이미지들을 활용해서 로컬 쿠버네티스를 통해서 올리는 게 목표

도커 컴포즈와 쿠버네티스 비교

  • docker-compose
    • 이미지들을 각각 빌드해서 올린다
    • 우리가 원하는 컨테이너를 만든다
    • 각 네트워크 속성들에 대해 정의를 각각 한다.
  • kubernetes
    • 이미 모든 이미지들이 만들어져있다고 가정한다
    • 우리가 만들기를 원하는 하나의 오브젝트에 하나의 설정을 준비한다
    • 우리는 모든 네트워크를 수동으로 설정해야 한다

우리는 도커 허브에 이미 올라가있는 이미지를 가져와서 쿠버네티스에서 설정하고 배포를 하는 과정을 진행을 할 예정이다.

포드 구성 파일 추가

  • docker-hub 에서 bear2u/multi-client 검색
  • 폴더를 새로 추가 (mkdir sample-k8s)
  • 설정 파일 추가
    • client-pod.yaml

apiVersion: v1 kind: Pod metadata: ## 1 name: client-pod labels: component: web spec: ## 2 containers: - name: client image: bear2u/multi-client ports: ## 3 - containerPort: 3000

  1. kind는 만들고자 하는 오브젝트 유형
  2. 다른 설정에서 불러와서 사용할 이름 정의
  3. 포드 내 콘테이너들 설정 정의를 한다
  4. 포드 내 콘테이너 포트를 정의를 한다. (내부용인걸 유념)

노드 설정 파일 추가

client-node-port.yaml

apiVersion: v1 kind: Service metadata: name: client-node-port spec: type: NodePort ports: # 1 - port: 3050 # 2 targetPort: 3000 # 3 nodePort: 31515 selector: # 4 component: web

서비스 형태의 설정파일을 추가한다.

  • kind : Service 정의해서 서비스 설정 파일인걸 체크
  1. port는 해당 서비스 포트를 뜻한다. 외부로 향하는 포트는 아니다.
  2. targetPort는 내부에 콘테이너 포트를 뜻한다.
  3. nodePort는 외부에서 접속시 들어올 수 있는 포트를 뜻한다.

현재 개발 서버이기 때문에 따로 RB(로드 발랜싱)은 지정하지 않는다.

설정파일

설정파일은 다음 4가지로 만들어질 수 있다.

  • StatefulSet
  • ReplicaController
  • Pod
  • Service

apiVersion은 다음 두가지 종류를 가진다.

image-20190208222316470

기본 적인 노드의 구성

image-20190208222519850

  • 포드는 하나이상의 콘테이너를 담고 있어야 한다
  • 하나의 포드에 여러개의 콘테이너를 담는 경우는 정말 긴밀하게 연결되어 있는 경우를 생각하자

설정 파일 타입

image-20190208223230713

바람직한 예

image-20190208222759200

구성도 (미니 큐브)

image-20190208223333452

image-20190208223526446

image-20190208223630053

설정 파일 선언

이제 설정 파일 두개를 kubectl 를 통해서 선언을 하도록 하자.

kubectl apply -f client-pod.yaml
kubectl apply -f client-node-port.yaml

pods & services

설정한 내용에 대해서 포드 동작되는 지 체크할려면 아래와 같이 할 수 있다

kubectl get pods

.......
client-pod                            1/1       Running   1          1h

서비스에 대해서도 가져올 수 있다

kubectl get services

........
client-node-port              NodePort    10.109.106.191   <none>        3050:31515/TCP   6d

웹 테스트

이제 서버에 동작이 잘 되는지 체크하자.

우선 IP를 가져오자. vm box를 통해서 가져와야 하기 때문에 머신을 통해야 한다

minikube ip

........
192.168.99.100

이제 실행해보자. 웹 브라우저에 다음과 같이 쳐서 들어갈 수 있다

http://192.168.99.100:31515/

전체 배포 흐름

image-20190208224323680

참고

  • docker ps 를 하면 현재 실행되고 있는 콘테이너들이 보인다. 그걸 죽이더라도 쿠버네티스가 다시 시작한다.
  • 선언형으로 이루어져서 운영자는 마스터에 설정파일을 적용하면 스케쥴로 인해 포드수를 서서히 올리거나 줄이도록 한다.
  • 물론 명령형으로 수동으로 올리거나 내릴순 있지만 추천은 안함


로그인 수업

목표

한빛 출반 홈페이지에 접속해서 로그인 과정을 거쳐서 마일리지 와 코인정보를 가져오는 게 목표

준비

  1. 파이썬
  2. vscode
  3. requests
  4. beautifulsoup

한빛 출반 네트워크 로그인 과정 뚫어보기

로그인 페이지 분석

<div class="w940 wrap_member">
		<!-- 로그인 영역 -->	
		<form name="frm" id="frm" action="#" method="post">
		<input name="retun_url" id="retun_url" type="hidden" value="http://www.hanbit.co.kr/index.html" class="i_text" size="100">
		<div class="login_left">		
			<fieldset>
				<legend>한빛출판네트워크 로그인</legend>
				
				<label class="i_label" for="login_id"><strong style="position: absolute; visibility: hidden;"></strong>
					<input name="m_id" id="m_id" type="text" value="" class="i_text" placeholder="아이디" onkeydown="javascript:if(event.keyCode==13){login_proc(); return false;}">
				</label> 

				<label class="i_label" for="login_pw"><strong style="position: absolute; visibility: hidden;"></strong>
					<input name="m_passwd" id="m_passwd" type="password" value="" class="i_text" placeholder="비밀번호" onkeydown="javascript:if(event.keyCode==13){login_proc(); return false;}">
				</label>
				
				<label>
					<input type="button" name="login_btn" id="login_btn" value="로그인" class="btn_login">					
				</label>
				
				<label class="i_label2">
					<input type="checkbox" name="keepid" id="keepid" value="1" class="i_check"><strong>아이디 저장</strong>
				</label>
			</fieldset>
			
			<ul class="login_btn">
				<li><a href="/member/find_id.html" class="btn_idc">아이디 찾기</a></li>
				<li><a href="/member/find_pw.html" class="btn_pwc">비밀번호 찾기</a></li>
				<li><a href="/member/member_agree.html" class="btn_joinc">회원가입</a></li>
			</ul>
		</div>
		</form>
		<!-- //로그인 영역 -->
		
		<!-- 배너 -->
		<div class="login_right"><a href="/event/current/current_event_view.html?hbe_idx=88&amp;page=0" target="_blank">
					<img src="/data/banner/login1901psd.jpg" alt="소중한 친구에게 손글씨 엽서와 한빛 책을 선물하세요!" width="440" height="220">
				</a></div>
		<!-- //배너 -->
	</div>

우선 로그인되는 과정을 분석하기 위해서는 크롬에서 network / doc 탭에서 흐름을 체크를 하자.

preserve log 라는 곳에 체크를 해야 한다. 이유는 network 페이지내에서는 한번 새로 고침을 하는 순간 로그들이 없어지기 때문에 이전 로그들을 남겨야 할려면 체크를 하자.

1548983019451

로그인을 하는 순간

1548983082001

login.html -> login_proc -> index.html 순으로 흐르게 된다. 기본적인 로그인 흐름을 볼수 있다.

1548983369350

Request 항목에서 Form Data를 살펴보면 id, password 등이 주고 받는 걸 볼수 있다.

소스 작성

그럼 이제 본격 파이썬으로 진행해보도록 하자.

우선 패키지 먼저 설치를 하자

pip install beautifulsoup4 urljoin load_dotenv

그럼 소스 흐름은

  1. 우선 login_proc 에 post로 값을 던져서 세션정보를 가져오도록한다.
  2. 마이 페이지로 접속 후 마일리지와 코인 정보를 가져온다.

.env 파일을 만들고 거기에 USER_ID와 USER_PASSWORD를 지정해주자

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from dotenv import load_dotenv
import os

# 환경 변수 로드
load_dotenv()

# 아이디와 비밀번호 지정

USER_ID = os.getenv("USER_ID")
USER_PWD = os.getenv("USER_PASSWORD")

# 세션 시작하기
session = requests.session()

# 로그인하기
login_info = {
    "m_id": USER_ID,
    "m_passwd": USER_PWD
}

url_login = "http://www.hanbit.co.kr/member/login_proc.php"
res = session.post(url_login, data=login_info)
# 오류 발생시 예외가 발생한다
res.raise_for_status()

# 마이페이지 접근
url_mypage = "http://www.hanbit.co.kr/myhanbit/myhanbit.html"
res = session.get(url_mypage)
res.raise_for_status()

# 마일리지와 이코인 가져오기
soup = BeautifulSoup(res.text, "html.parser")
mileage = soup.select_one(".mileage_section1 span").get_text()
ecoin = soup.select_one(".mileage_section2 span").get_text()
print("마일리지: " + mileage)
print("이코인: " + ecoin)

실행을 해보면 최초 가입시 받는 2,000 코인이 잘 나오는 걸 확인이 가능하다.

> python login_test.py

마일리지: 2,000
이코인: 0

결론

이상으로 아주 간단한 로그인 스크래핑을 진행했다. 이 스터디는 매주 금요일에 진행되는 파이썬 자동화 수업 중 내용입니다.

React,MongoDB,Express,Nginx 도커 개발 환경 구성하기

이번 시간에는 대중적으로 많이 쓰이는 환경을 도커로 하나씩 구축해볼 예정이다.

구성 스택은 다음과 같다.

  1. Client : React
  2. Api Server : Node Express
  3. DB : Mongo
  4. Server : Nginx

Root

우선 환경을 구성한다. 기본적으로 3개의 폴더와 docker-compose.yaml 파일로 구성된다.

- client
	- ...sources
	- Dockerfile.dev
- nginx
	- default.conf
	- Dockerfile.dev	
- server
	- ...sources
	- Dockerfile.dev
- docker-compose.yaml	

Client

create-react-app 을 이용해서 구성한다.

- ./client

# create-react-app client

dockerfile.dev 파일을 만들어 준다.

# 노드가 담긴 alpine 이미지 가져오기
FROM node:10.15-alpine

//작업할 디렉토리 설정
WORKDIR "/app"

//npm install을 캐싱하기 위해 package.json만 따로 카피
COPY ./package.json ./
RUN npm install
 
// 소스 복사 
COPY . .

//client 소스 실행
CMD ["npm","run","start"]

Server

express generator를 이용해서 만들 예정이다.

npm install -g express-generator

express server

package.json에는 실시간 개발환경을 위해서 nodemon을 추가해주자.

  "scripts": {
    "dev": "nodemon ./bin/www",
    "start": "node ./bin/www"
  },

서버쪽에도 dockerfile.dev을 만들어보자

FROM node:10.15-alpine

WORKDIR "/app"

COPY ./package.json ./

RUN npm install -g nodemon \
 && npm install
 
COPY . .

CMD ["npm","run","dev"]

Nginx

프록시 서버로 동작될 예정이다.

client와 server 부분을 따로 프록시로 관리한다.

server는 / 로 들어오는 요청을 /api 로 바꿔준다.

파일 두개를 생성해주자

  • default.conf
# nginx/default.conf

upstream client {
    server client:3000;
}

upstream server {
    server server:3050;
}

server {
    listen 80;

    location / {        
        proxy_pass http://client;
    }

    location /sockjs-node {
        proxy_pass http://client;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }

    location /api {
        rewrite /api/(.*) /$1 break;
        proxy_pass http://server;
    }
}
  • Dockerfile.dev
# nginx/Dockerfile.dev

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf

Docker-compose

이제 기본 생성된 파일들을 묶어주고 간편하게 한번에 올릴 수 있게 하자.

  • docker-compose.yaml
version: "3"
services:
  client:                 
    build:
      dockerfile: Dockerfile.dev
      context: ./client    
    volumes:
      - ./client/:/app      
      - /app/node_modules  
    networks:
      - backend           
  server:                          
    build:
      dockerfile: Dockerfile.dev
      context: ./server    
    volumes:
      - ./server/:/app      
      - /app/node_modules      
    environment:
      - NODE_PATH=src
      - PORT=3050
      - DB_HOST=mongo
      - DB=test
      - REDIS_HOST=redis
      - REDIS_PORT=6379      
    networks:
      - backend  
    depends_on:
      - mongo
      - redis 
    ports:
      - "5000:3050"   
  redis:
    container_name: redis
    image: redis
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    networks:
      - backend 
    volumes:
      - data:/data/redis   
    ports:
      - "6379:6379"
    restart: always    
  mongo:
    container_name: mongo
    image: mongo
    volumes:
      - data:/data/db
    ports:
      - "27017:27017" 
    networks:
      - backend

  nginx:
    restart: always
    build:
      dockerfile: Dockerfile.dev
      context: ./nginx 
    ports:
      - '3000:80' 
    networks:
      - backend
    

networks: 
  backend:
    driver: bridge

volumes:
  data:
    driver: local  

위 내용을 잠깐 설명하자면 기본적으로 서비스는 5개를 실행하고 있다.

  • client
  • server
  • mongo
  • redis
  • nginx

이 많은 서비스를 저 파일 하나로 묶어서 배포를 할수 있다. 멋지다.

자 그럼 실행해서 잘되는 지 체크 해보자.

실행

# --build는 내부 Dockerfile 이 변경시 다시 컴파일 해준다. 
docker-compose up --build or docker-compose up

docker-compose stop or docker-compose down

일단 이상태로는 기본 환경만 올라간 상태라서 뭘 할수 있는 건 아니다.

소스를 좀 더 추가해서 연동을 해보자.

Client

  • App.js
    • npm install axios
import React, { Component } from "react";
import axios from "axios";
import Items from './Items';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      content: "",
      change: false
    };

    this.handleChange = this.handleChange.bind(this);
    this.sumbit = this.sumbit.bind(this);
  }

  handleChange(event) {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    console.log(name, value);

    this.setState({
      [name]: value
    });
  }

  async sumbit(event){
    event.preventDefault();
    console.log(`${this.state.title},${this.state.content}`);

    await axios
      .post("/api/todos", {
        title: this.state.title,
        content: this.state.content
      })
      .then((result) => {
        this.setState({
          change : true
        });
      })
  }

  render() {
    return (
      <div style={{ margin: "100px" }}>
        <div>
          <form>
            <p>투두리스트</p>
            <input type="text" name="title" onChange={this.handleChange} />
            <br />
            <textarea name="content" onChange={this.handleChange} />
            <button onClick={this.sumbit}>전송</button>
          </form>
        </div>
        <Items change={this.state.change}/>
      </div>
    );
  }
}

export default App;
  
await axios
      .post("/api/todos", {
        title: this.state.title,
        content: this.state.content
      })

여기에서 /api를 통해서 바로 도커내 api서버로 접근을 할 수 있다.

그리고 추가된 리스트는 Items.js 컴포넌트를 만들어서 관리한다.

import React, { Component } from "react";
import axios from "axios";

class Items extends Component {
  constructor(props) {
    super(props);
    this.state = {
        Todos: []
    }
  }

  componentWillReceiveProps(props) {
      console.log(props);
      this.renderTodos();
  }

  componentDidMount() {
      this.renderTodos();
  }

  async renderTodos() {

    try {
        let todos = await axios.get("/api/todos");

        this.setState({
            Todos: todos.data.map(todo => {
                console.log(this.getItem(todo));
                return this.getItem(todo);
            })
        });
    } catch (err) {
        console.log(err);
    }
  }

  getItem(todo) {
    //   console.log(todo);
    return (
      <div key={todo._id}>
        <div style={{ padding: "10px", border:"1px solid red" }}>          
          <div>{todo.title}</div>
          <div>{todo.content}</div>
          <div>{todo.regdate}</div>
        </div>
      </div>
    );
  }

  render() {
    return <div>{this.state.Todos}</div>;
  }
}

export default Items;

결과 화면

image-20190119144313239

아주 심플하지만 작동은 잘 된다. 서버쪽 소스도 업데이트를 해야 잘 나오는 것 명심하자.

Server

Server에서 추가 사항은 몽고 디비와 연동해서 데이터 주고 받는 부분이다.

  • app.js
    • npm install mongoose
  var createError = require('http-errors');
  var express = require('express');
  var path = require('path');
  var cookieParser = require('cookie-parser');
  var logger = require('morgan');
  var cors = require('cors');
  var mongoose = require('mongoose');
  
  var db = mongoose.connection;
  db.on('error', console.error);
  db.once('open', function(){    
      console.log("Connected to mongod server");
  });
  
  mongoose.connect('mongodb://mongo/todo');
  
  var indexRouter = require('./routes/index');
  var usersRouter = require('./routes/users');
  var todoRouter = require('./routes/todo');
  
  var app = express();
  
  //Cors 설정
  app.use(cors());
  
  // view engine setup
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'jade');
  
  app.use(logger('dev'));
  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(cookieParser());
  app.use(express.static(path.join(__dirname, 'public')));
  
  app.use('/', indexRouter);
  app.use('/users', usersRouter);
  app.use('/todos', todoRouter);
  
  // catch 404 and forward to error handler
  app.use(function(req, res, next) {
    next(createError(404));
  });
  
  // error handler
  app.use(function(err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};
  
    // render the error page
    res.status(err.status || 500);
    res.render('error');
  });
  
  module.exports = app;
  
  • todo.js
var express = require('express');
var router = express.Router();

var Todo = require('../models/todo');

/* GET home page. */
router.get('/', function(req, res, next) {
  return Todo.find({}).sort({regdate: -1}).then((todos) => res.send(todos));
});

router.post('/', function(req, res, next) {
  const todo = Todo();
  const title = req.body.title;
  const content = req.body.content;

  todo.title = title;
  todo.content = content;

  return todo.save().then((todo) => res.send(todo)).catch((error) => res.status(500).send({error}));
});

module.exports = router;
  • model/todo.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var todoSchema = new Schema({
    content: String,
    title: String,
    isdone : Boolean,
    regdate: { type: Date, default: Date.now  }
});

module.exports = mongoose.model('todo', todoSchema);

마무리

다시 docker-compose up --build로 통해서 올려보면 작동이 잘 되는 걸 볼수 있다.

전체 소스는 여기에서 받을수 있다.

이상으로 GDG 부산에서 진행하는 도커 & 쿠버네티스 스터디 내용이었습니다.

참가해주셔서 감사합니다.

Flutter + Steho 사용하기

Flutter 로컬 디비 사용시 대중적인 방법은 sqflite 일것이다.

일반 sqlite 와 문법이 똑같으며 사용하기에도 편하다.

sqlite 를 모바일에서 연동시 페이스북에서 나온 steho 라는 라이버러리가 있다.

안드로이드만 현재 가능하지만 꽤 유용하게 사용가능하다.

그럼 flutter 에서 어떻게 적용하는지 살펴보자.

우선 sqflite 를 import 로 가져오고 생성시 getDatabasePath() 로 가져올 수 있다.

  Future<Database> init() async {
    String documentsDirectory = await getDatabasesPath(); //주의하자.
    final path = join(documentsDirectory, "test.db");
    final db = await openDatabase(
      path,
      version: _version,
      onCreate: _onCreate,
      onUpgrade: (Database db, int oldVersion, int newVersion) async {
        if(newVersion > oldVersion) {
          await File(path).delete();
          _onCreate(db, newVersion);
          print('onUpgrade done');
        }
      }
    );
    return db;
  }

그리고 stetho 라이버러리를 설치를 하자.

마지막으로 main.dart 에서 적용하면 된다.

  if(isInDebugMode) {
    Stetho.initialize();
  }

중요한 점은 디버깅 모드일 경우에만 실행을 해야 한다.

그래서 isInDebugMode 변수를 지정했다.

bool get isInDebugMode {
  bool inDebugMode = false;
  assert(inDebugMode = true);
  return inDebugMode;
}

크롬에서 chrome://inspect/#devices 주소를 열어서 왼쪽 Devices 항목을 클릭시 inspect 항목이 나오는데 그걸 클릭시 새 창이 뜨면서 확인이 가능하다.

Android 만 가능한걸로 안다. 그 점을 유의하자.

 

이상으로 Flutter + Sqflite + Stetho 적용법을 알아보았다.

 

2018년 회고

서론

나에게 2018년에 있어서는 정말 소중하고 열심히 살았던 해였던 것 같다. 나름 많은 스터디와 새로운 커뮤니티 활동등을 통해서 많은 분들과 인연을 맺었으며 전공 공부도 덩달아 많이 했던 시기였던 것이다.

커뮤니티 행사

부산에는 지난 2년간 큰 개발자 행사가 열리지는 않았던 것 같다. 이 점을 상당히 안타깝게 생각하는 중 나에게 GDG 부산 운영자를 할 수 있는 기회가 오게 되었다. 누구 하나 나서야 하는 상황에서 일단 나라도 시작을 했다.

  • 부산 Google IO Extended 2018 (8월) - 70명

  • 부산 GDG Devfest 2018 (11월) - 300명

  • 부산 Android 라이트닝 토크 (9월) - 30명

  • 부산 Flutter 스터디 잼

  • 부산 Tensorflow 스터디 잼

이 많은 행사를 우리 GDG 맴버들이 해낸것이다. 자랑스럽게 생각한다.

2019년도에도 더 열심히 활동해서 부산 개발자 커뮤니티 발전에 도움이 되었으면 한다.

스터디

수도권이 아닌 지방이라서 그런지 개발 스터디가 너무 없다. 그래서 나도 공부도 할 겸 스터디를 시작하였다. 내가 공부한 내용을 공유하는 강의식도 있었고 같이 공부하는 스터디도 있었다.

일주일에 4일 이상을 진행을 했었고 지금도 진행중이다. 힘들긴 해도 돌아보면 정말 많은 걸 배우고 보람이 있는 시간이었다.

2018년도는 블록체인을 좀 많이 공부했다.

  • 안드로이드 스터디

    • 배달앱 클론 강의식 스터디( Android + MVP + RXJAVA + Retrofit)

    • 안드로이드 프로젝트 스터디

  • 머신러닝

    • 모두의 딥러닝 ( 인프런 )

    • 텐서플로JS 스터디 (유데미)

  • 블록체인

    • 바닥부터 자바스크립트로 배우는 블록체인 코어 스터디

    • 알트 코인 개발 스터디

    • 블록체인 코어 개발 스터디

    • 이더리움 DAPP 개발 스터디

    • 고랭으로 구축하는 블록체인

    • Advance CSS 스터디

  • Flutter ( 요즘 내가 완전 빠져 있는 모바일 플랫폼)

    • Flutter 입문 스터디

    • Flutter 프로젝트 스터디

      • 미니 유튜브 클론

      • 미니 채팅앱 클론

    • Flutter 강의식 스터디 (진행중)

  • 인프라

    • 도커로 이용해서 CI / CD 스터디

      • AWS Beanstalk 배포

      • 도커라이징하는 방법 스터디

      • Dockfile 작성 공부

    • 쿠버네티스 입문 스터디

      • minikube 통해서 로컬 배포

      • pod 개념 스터디

      • GCP 에 배포

      • helm 적용

      • ingress nginx 적용

개발 스터디를 2018년에 대략 50~70번 넘게 진행하면서 느낀 점은 대략 이렇다.

  • 맴버들이 바빠서 공부를 해서 발표하는 것이면 서로 좀 힘들고 지속되기 힘들다.

  • 같이 영상을 보는게 집중도 잘 되고 서로 이야기도 좀 하면서 유익한 스터디가 될 수 있다.

  • 강의식으로 진행시 나에게도 한번 더 정리할 기회가 되므로 유익하다.

  • 꾸준하게 진행하는 게 정말 중요하다.

    • 다른 스터디들 다녀보니 중간에 파토 나는 경우가 많은데 이유는 장소도 문제이지만 사람이 점점 줄어든다. 그래서 내가 진행하는 스터디는 특별한 이유 없이시는 중단 하지 않는다.

    • 비용까지 받아서 책임감이 생겨서 그런지 꾸준히 하게된다..;;

물론 반성하는 것도 있다.

  • 좀 더 매력적인 스터디를 만들도록 노력해야 겠다.

  • 깊이 있는 스터디를 준비해서 진행한다. ( 문제는 신청하는 사람이 없다..ㅠㅠ)

  • 스터디 장소 문제 (조금 외곽이라서 ㅠㅠ)

올해는 머신러닝에 좀 더 집중해서 진행할 예정이다. 이미 생활패턴이 스터디에 적응되서 일주일에 몇번 하든 크게 문제는 없을 것 같다.

인증 공부

온라인에서 마음 맞는 분들과 다양한 전공 공부를 하고 있다.

2019년에는 현재 3기가 진행중이다. 이미 많은 분들이 2기까지 무사히 끝내신 분들이 많다.

100일동안 꾸준히 전공공부를 한다는 건 생각보다 쉽지 않은 편이다.

그래도 같이 공부하고 서로 격려하면서 쉽지 않은 길을 가니 끝까지 갈 수 있는 것 같다.

현재 3기는 약 50일까지 진행되었는데 그 내용은 한번 정리를 해볼까 한다.

현재 공부한 시간은 7,910분을 공부했으며 51일차에 들어갔다.


결론

2018년에는 주로 모바일, 블록체인 쪽을 많이 공부했던 것 같다. 2019년에는 머신러닝Flutter 에 좀 더 집중해서 공부할려고 한다.

블로그 정리도 12월에는 좀 더디게 진행 했는데 다시 마음 잡고 정리를 하면서 복습도 같이 하도록 하겠다.

이상으로 2018년을 마무리하면서 회고란걸 적어보았다.

올 한해도 화이팅하자!!

Flutter Codelab을 돌려볼때 오류가 발생될 경우가 있다. 

코드랩 위치 : https://codelabs.developers.google.com/codelabs/mdc-101-flutter


오류 내용은 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

해결방법은 2가지를 체크 해야 한다. 

1. android 폴더내에 project 레벨에서 build.gradle 에서 dependencies 가 3.1 이상으로 되어있는 지 체크하자. 

dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}

2. android/gradle/wrapper 폴더내 gradle-wrapper.properties 에 4.6으로 설정

distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip


이 두가지를 설정시 별 문제 없이 빌드 되는 걸 볼수 있다. 

이상으로 해결방법에 대해 공유해본다. 

Android 에뮬레이터 실행시 

Emulator: Process finished with exit code -1073741819 (0xC0000005)

라는 에러로 실행이 되지 않는 경우 

avd config 파일에서 hw.gpu.mode = off

출처 : https://stackoverflow.com/a/51740954

지치지 않고 제대로 공부하는 7가지 방법 요약

동영상 링크

https://www.youtube.com/watch?v=P2rpSiACoVQ&t=890s


어떻게 해야 우울해 지지 않고 하루에 10시간 이상 공부할 수 있을까? 어떻게 해야 그것에 익숙해질 수 있을까?

이에 대해 쿼라 에서 유명 교수님의 답변을 요약 한 내용입니다.

  1. 나는 당신은 그것을 할수 없다고 생각한다.

  2. 나는 당신이 그것을 시도해서도 안된다고 생각한다.

  3. 아무도 그렇게 많이 공부하지 않는다.

사람이 하루에 3시간이상 열심히 집중하는 건 매우 드물고 바람직하지 않는다

하지만 그래도 꼭 하고 싶다면 다음 7가지 내용을 기억하자.

꾸준하고 제대로 된 공부법은 무엇인가?

1. 하루에 7.5 이상 공부하지 마세요.

당신은 매우 지칠 것이다. 더한다고 해도 도움이 되지 않는다.

당신이 지쳤을 때 당신은 배울수 없다.

2. 일주일에 하루를 쉬세요.

쉬는 날 당신이 좋아하는 것을 쉬세요. 당신의 일은 배움을 위한 것이지 일찍 죽을려고 하는 것이 아니다.

휴식하는 동안에도 머리는 공부하고 있다. 힘들거나 잘 안풀리면 그냥 휴식을 하자.

3. 공부 계획를 세우세요.

각 과목에서 가장 중요한 지식은 무엇인지를 파악하고 그것에 대해 가장 쉽고 효율적으로 공부할 수 있는 공부방법에 대한 계획을 세우도록 하자.

목표를 세우는 이유

  • 무엇을 해야 하는지 알려준다.

  • 동기부여 수단

  • 현재 모습을 구체적으로 보게한다.

4. 낮은 난이도에서 높은 난이도 순으로 공부하세요.

큰틀에서 핵심 아이디어에 익숙해지고 그런 다음 세부 사항에 집중하자.

공부한 다음 피드백으로 그 내용을 다시 요약해보는 습관을 가져보자.

처음부터 디테일 집중하지 말고 크게 보고 작게 넘어가자.

그리고 작은 성공을 많이 이루어 내서 성공의 경험을 많이 맛보자.

기대 수준을 낮춰 작은 성공에 도전하자.

반꼴찌 40등 -> 5등은 어렵다.

하지만 40등 -> 30등은 쉬우므로 성취감도 느끼고 큰틀에서 먼저 공부하는 방식

5. 낮잠을 많이 자자

당신의 기분을 새롭게 하고 당신이 공부 한 것을 기억할 확률을 증가시킨다.

잠자는 것은 메모를 통합시키는 데 도움이 된다.

6. 2.5시간 동안 한 주제를 공부하세요.

그런 다음 다른것으로 전환하자. 이어서 계속 반복하자.

2.5시간동안 한 주제를 반복 공부하자.

7. 읽은 내용 내용을 요약하자.

어렵게 공부하면 잊어지기가 어렵다.

책을 읽고 책을 내려놓고 읽은 것을 요약하자.

요약할 때는 읽은 내용을 보지말고 하자.

동일한 자료를 반복적으로 보지 않고 기억해내는 연습을 해야한다.

자료를 복습하는 건 아무 소용이 없다.

작업의 실제적인 어려움 없이 일하는 것이다. (책을 자꾸 들춰보면서 보는 방법 비추천)

책을 읽을때도 강조를 표시하거나 밑줄을 긋지 말도록 하자. (집중력 저하)

요약 = 요약, 암송, 토론, 발표, 글쓰기등등

공부한 내용을 어떻게든 밖으로 표출하는 것을 추천한다.

힘들지만 계속 하다보면 쉬워진다 = 뇌의 가소성

정리

1,2는 최대 7.5시간 공부하고 휴식하자.

3,4는 계획을 세우고 늦은 난이도에서 높은 난이도로 공부하도록 하자.

5는 낮잠으로 재충전하자.

6는 한 주제 최대 7.5시간

7은 요약하기 (어렵게 공부하면 잊기가 어렵다)

책상에만 앉아 꼼짝않고 책만 보는 건 공부 하수가 하는 일

추천 공부 방법

7.5시간 공부 + 쉬는 시간 확보 +낮잠 + (커피 + 운동)= 공부 효율성 극대화

추천 책 리스트

  1. 완벽한 공부법

  2. 혼자하는 공부법




Tensorflow JS 스터디 4주차

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/machine-learning-with-javascript 을 같이 보면서 공부중입니다.

이전 알고리즘과 비교

  • 어떤 버킷에 공을 놓을때 어떤 위치를 갈지 예측하기 -> Classification

    • Classification 은 예를 들어 해당 메일이 스팸인지 일반 메일인지 판별하는 문제 해결시 사용된다.
  • 주변 시세에 따른 주택 가격 예측 -> Regression (선형 회귀)

주택 가격 예측 (by knn)

1544689793285

KNN 알고리즘

  • 이번 스터디는 텐서플로를 이용해서 KNN 알고리즘 하나하나 구현해본다.

1544690154341

  • 특정 기능과 예측포인트 사이를 찾고
  • 작은 것에서 큰걸로 정렬한다
  • 최상위 K 레코드들을 가져온다.
  • 이러한 최상위 레코드들을 가져온 것에서 평균을 내서 결과값 도출한다.

목표

  • 위도 경도가 주어지면 House Price 가격을 주어진 데이터에 근거해서 뽑아내는 게 목표

준비단계

1544690376477

이전 내용에서는 각각 줄마다 합쳐서 테이블을 만들었다면 이번에는 두개의 features 와 labels 로 나누어서 작업을 할 예정이다.

  • 가짜 데이터들을 먼저 만들도록 한다.

1544690502504

실습

먼저 두개의 별도의 텐서를 만든다. 하나는 위도와 경도를 저장하는 것이고 또 하나는 주택 가격을 저장한다.

const features = tf.tensor([
	[-121, 47],
  [-121.2, 46.5],
  [-122, 46.4],
  [-120.9, 46.7]  
]);
const labels = tf.tensor([
  [200],
  [250],
  [215],
  [240]
]);
const predictionPoint = tf.tensor([-121, 47]);

knn #1 features 와 predictionPoint 와 거리를 구한다.

1544691170114

1544691187998

거리를 구하는 방법을 텐서플로로 진행 해보자.

1544691243831

이 계산식을 하나씩 풀어서 진행해볼 예정이다. 그럼 시작해보자.

Distance Sub

1544691388465

...

features
  .sub(predictionPoint)
[[0 , 0 ], [-0.1999969, -0.5 ], [-1 , -0.5999985], [0.0999985 , -0.2999992]]

제곱

1544691577938

features
  .sub(predictionPoint)
  .pow(2)
[[0 , 0 ], [0.0399988, 0.25 ], [1 , 0.3599982], [0.0099997, 0.0899995]]

Sum

  • 각각의 행에 대한 텐서의 값을을 합산한다.
  • Sum(0 or 1)
    • 0의 경우 메인 축이 세로로 지정된다.
    • 1의 경우 세로측이 메인이다.

1544691705955

features
  .sub(predictionPoint)
  .pow(2)
  .sum(0)
[1.0499985, 0.6999977]

우리가 원하는 가로로 계산하길 원한다.

features
  .sub(predictionPoint)
  .pow(2)
  .sum(0)
[0, 0.2899988, 1.3599982, 0.0999992]

1544692088645

제곱근

1544692117081

features
  .sub(predictionPoint)
  .pow(2)
  .sum(1)
  .pow(0.5)
[0, 0.5385153, 1.1661896, 0.3162265]

정렬

KNN 알고리즘 순서 두번째로 정렬을 진행한다.

  • 텐서들은 정렬할 수 없는 점 유의

1544692330245

  • 거리와 라벨을 따로 떼서 정렬하면 안된다. 아래그림같이 문제가 될 수 있다.

1544692420312

Concat

두개의 텐서들을 합치기 위해 concat 을 이용한다.

1544692526090

1544692556011

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.concat(labels)
error : Error in concat1D: rank of tensors[1] must be the same as the rank of the rest (1)

shape 들이 브로드 캐스팅을 하기 위한 조건이 아니어서 발생을 한다.

labels
	.shape

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.shape
	
..... 브로드 캐스팅 조건이 맞지 않는다.
[4,1]
[4]

ExpandDims

  • 부족한 줄을 채워 주기 위해 expandDims() 를 적용한다.
features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims()

...
[[0, 0.5385153, 1.1661896, 0.3162265],]

1544692996609

한칸 더 생긴걸 볼수 있다.

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims()
	.shape
...
[1,4]

아직도 label 쪽과 맞지 않는다.

  • Axis 를 1로 해서 바꿔준다.
...
.expandDims(1)
	
...
[4,1]

이제 다시 concat 을 해보자

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels)
	
....
[[0 ], [0.5385153], [1.1661896], [0.3162265], [200 ], [250 ], [215 ], [240 ]]

하지만 결과를 보면 [feature,label] 순으로 합쳐 있지 않다.

  • concat 시 축을 1로 바꿔서 해보자

1544693281622

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	
....
[[0 , 200], [0.5385153, 250], [1.1661896, 215], [0.3162265, 240]]

이제 정상적으로 합쳐진걸 볼수 있다.

하지만 아직 정렬에서 문제가 있다.

1544693511557

현재 이런 구조인데 이 상태로 정렬이 안된다는 것이다. 이걸 해결하기 위해서 unstack 이라는 함수로 각각의 텐서로 쪼개어서 정렬할 수 있다.

unstack

1544693583648

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()

1544693730949

각각의 텐서들로 쪼개어진 걸 볼수 있다.

만약 특정 위치의 값을 뽑기 위해선 배열형태로 접근이 가능하다.

...
.unstack()[3]
...
[0.3162265, 240]

정렬의 문제

일반 오브젝트 형태를 정렬시 동작이 안된다. 아래는 잠깐 예를 들어본다

const distance = [
  { value: 10 },
  { value: 30 },
  { value: 20 }
];

distance.sort()
........
[{"value":10},{"value":30},{"value":20}]
  • sort 함수를 좀 더 확장해서 정렬을 할 수 있다.
const distance = [
  { value: 10 },
  { value: 30 },
  { value: 20 }
];

distance.sort((a,b) => {
	return a.value > b.value ? 1 : -1
})

.......
[{"value":10},{"value":20},{"value":30}]

그럼 이어서 unstack 한 부분에 적용을 해보자.

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()
	.sort((a, b) => {
		a.get(0) > b.get(0) ? 1 : -1
	})

최상위 K 레코드 가져오기

정렬된 배열에서 최상위 오브젝트들을 가져오기 위해 slice 을 사용한다.

const k = 2;

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()
	.sort((a, b) => {
		a.get(0) > b.get(0) ? 1 : -1
	})
	.slice(0,k)
...

[{"isDisposedInternal":false,"shape":[2],"dtype":"float32","size":2,"strides":[],"dataId":{},"id":7895,"rankType":"1"},
{"isDisposedInternal":false,"shape":[2],"dtype":"float32","size":2,"strides":[],"dataId":{},"id":7897,"rankType":"1"}]

평균 구하기

합계 ( reduce ) / count

const distance = [
  { value: 10 },
  { value: 30 },
  { value: 20 }
];

distance.reduce((acc,obj) => {
	return acc + obj.value
}, 0)

60 / 3(distance length)
.......
60 -> 20(결과)

위의 예제를 토대로 다시 본 소스에 적용해보자.

const k = 2;

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()
	.sort((a, b) => {
		a.get(0) > b.get(0) ? 1 : -1
	})
	.slice(0,k)
	.reduce((acc, pair) => acc + pair.get(1), 0) / k;
	
...	
225

그럼 labels 기준으로 했을때 가장 근접한 수치가 225 로 뽑을수 있다.

여기까지가 텐서플로을 이용해서 KNN 알고리즘을 적용하는 방법이었다.

그럼 이제 다음 내용에는 실제 데이터를 가지고 작업을 해보자.

소스 준비

git clone https://github.com/StephenGrider/MLKits.git
git checkout 84f617c4549409c104aa994465101d1a8fd164e2

npm install

1544706669880

Index.js

이제 위에서 공부했던 내용을 코드로 옮겨보자.

기본적으로 모듈은 두개를 설치를 한다.

require('@tensorflow/tfjs-node');
// 만약 gpu 를 사용시 tfjs-node-gpu 적용
const tf = require('@tensorflow/tfjs');

load csv file

이미 준비된 데이터를 이용한다.

require('@tensorflow/tfjs-node');
const tf = require('@tensorflow/tfjs');
const loadCSV = require('./load-csv');

let { features, labels, testFeatures, testLabels } = loadCSV('kc_house_data.csv', {
    // 무작위로 섞기
    shuffle: true,
    // 테스트셋 지정 -> testfeatures, testlabels
    splitTest: 10,
    // features 지정 (위도, 경도) -> features
    dataColumns: ['lat', 'long'],
    // price 지정 -> label
    labelColumns: ['price'],    
});

console.log(testFeatures);
console.log(testLabels);
[ [ 47.561, -122.226 ],
  [ 47.6595, -122.186 ],
  [ 47.5081, -122.093 ],
  [ 47.5276, -122.161 ],
  [ 47.6695, -122.333 ],
  [ 47.6769, -122.36 ],
  [ 47.5442, -122.141 ],
  [ 47.699, -122.206 ],
  [ 47.3196, -122.399 ],
  [ 47.2843, -122.357 ] ]
[ [ 1085000 ],
  [ 466800 ],
  [ 425000 ],
  [ 565000 ],
  [ 759000 ],
  [ 512031 ],
  [ 768000 ],
  [ 1532500 ],
  [ 204950 ],
  [ 247000 ] ]

Tensorflow JS 작성

function knn(features, labels, predictionPoint, k) {
    return features
        .sub(predictionPoint)
        .pow(2)
        .sum(1)
        .pow(0.5)
        .expandDims(1)
        .concat(labels, 1)
        .unstack()
        .sort((a, b) => {
            a.get(0) > b.get(0) ? 1 : -1
        })
        .slice(0, k)
        .reduce((acc, pair) => acc + pair.get(1), 0) / k;
}

knn 함수 적용

  • 변수들을 텐서로 변환해서 사용해야 한다
features = tf.tensor(features);
labels = tf.tensor(labels);
testFeatures = tf.tensor(testFeatures);
testLabels = tf.tensor(testLabels);
  • 하나의 값만 우선 테스팅 하기 위해 testFeatures[0] 으로 가져온다.
const result = knn(features, labels, tf.tensor(testFeatures[0]), 10);
console.log('Guess', result, testLabels[0][0]);

.....
Guess 559100 1085000

하지만 559100 1085000 두개의 값이 가격 차이가 너무 나는 걸 볼 수 있다.

이걸 다시 개선하는 방법에 대해서 공부해보자.

정확도

  • 우선 에러가 작을수록 좋다. 이걸 측정해보자.

1544708102786

위의 공식을 코드로 구현하면 다음과 같다.

const err = (testLabels[0][0] - result) / testLabels[0][0];
console.log('Err', err * 100);

.......
Err 48.47004608294931

약 48프로 정도 에러가 발생하고 있다.

그럼 전체 testFeatures 에 대해 불정확률을 구해보면

testFeatures.forEach((testPoint, i) => {
    const result = knn(features, labels, tf.tensor(testPoint), 10);
    const err = (testLabels[i][0] - result) / testLabels[i][0];
    console.log('Err', err * 100);
});

....
Err 48.47004608294931
Err -19.77292202227935
Err -31.55294117647059
Err 1.0442477876106195
Err 26.337285902503293
Err -9.192607478844055
Err 27.200520833333336
Err 63.51712887438825
Err -172.79824347401805
Err -126.35627530364373

이러한 내용에 좀 더 정확도를 올리기 위해 좀 더 추가적인 features 를 추가해본다.

  • sqft_lot : 평방 피트
let { features, labels, testFeatures, testLabels } = loadCSV('kc_house_data.csv', {
    // 무작위로 섞기
    shuffle: true,
    // 테스트셋 지정 -> testfeatures, testlabels
    splitTest: 10,
    // features 지정 (위도, 경도) -> features
    dataColumns: ['lat', 'long', 'sqft_lot'],
    // price 지정 -> label
    labelColumns: ['price'],
});

표준화

처음에 배웠던 내용중에 normalize 하는 방법에 대해서 공부를 했었다.

1544708829630

표준화 하는 과정

1544708882008

이 방법을 지금 내용에 도입해보자.

  • 중간에 너무 범위가 커서 제대로된 표준화가 힘들어 보이는 문제점이 있다

1544709183947

이 부분을 해결하기 위해서 새로운 함수를 적용시켜 보자.

  • StandartDeviation : 표준편차

1544709363419

moments

tensorflowjs 에는 함수를 제공 한다. => moments

예시를 들어보자.

const numbers = tf.tensor([
	[1, 2],
  [3, 4],
  [5, 6]
]);

tf.moments(numbers);

....
{"mean":{"isDisposedInternal":false,"shape":[],"dtype":"float32","size":1,"strides":[],"dataId":{},"id":41,"rankType":"0"},"variance":{"isDisposedInternal":false,"shape":[],"dtype":"float32","size":1,"strides":[],"dataId":{},"id":50,"rankType":"0"}}

우리는 여기에서 mean 과 variance 를 사용할 수 있다.

  • average : mean

  • value : numbers

  • StandartDeviation : sqrt(variance)

1544709860334

const numbers = tf.tensor([
  [1, 2],
  [3, 4],
  [5, 6]
]);

// 0은 축을 바꿔서 계산한다.
const { mean, variance} = tf.moments(numbers, 0);

numbers.sub(mean).div(variance.pow(.5))

.........
[[-1.2247449, -1.2247449], [0 , 0 ], [1.2247449 , 1.2247449 ]]

이제 본 소스에 적용해보자.

function knn(features, labels, predictionPoint, k) {
    const { mean, variance } = tf.moments(features, 0);

    const scaledPrediction = predictionPoint.sub(mean).div(variance.pow(0.5))

    return features
        .sub(mean)
        .div(variance.pow(0.5))
        .sub(scaledPrediction)
        .pow(2)
        .sum(1)
        .pow(0.5)
        .expandDims(1)
        .concat(labels, 1)
        .unstack()
        .sort((a, b) => {
            a.get(0) > b.get(0) ? 1 : -1
        })
        .slice(0, k)
        .reduce((acc, pair) => acc + pair.get(1), 0) / k;
}
Error -15.323502304147466
Error -11.344580119965723
Error -2.047058823529412
Error 19.327433628318584
Error 7.806324110671936
Error -14.106372465729613
Error -8.782552083333334
Error 13.227406199021207
Error -36.336911441815076
Error 7.381578947368421

30프로정도 확률안에서 도는 정도까지 수정했다.

이 보다 정확도를 올리는 방법은 두가지가 있다.

  • testFeatures 수를 좀 더 수를 늘리거나
  • 훈련수를 늘리거나

이건 다음 알고리즘 수업을 통해서 공부를 해보자.

전체 소스

require('@tensorflow/tfjs-node');
const tf = require('@tensorflow/tfjs');
const loadCSV = require('./load-csv');

function knn(features, labels, predictionPoint, k) {
  const { mean, variance } = tf.moments(features, 0);

  const scaledPrediction = predictionPoint.sub(mean).div(variance.pow(0.5));

  return (
    features
      .sub(mean)
      .div(variance.pow(0.5))
      .sub(scaledPrediction)
      .pow(2)
      .sum(1)
      .pow(0.5)
      .expandDims(1)
      .concat(labels, 1)
      .unstack()
      .sort((a, b) => (a.get(0) > b.get(0) ? 1 : -1))
      .slice(0, k)
      .reduce((acc, pair) => acc + pair.get(1), 0) / k
  );
}

let { features, labels, testFeatures, testLabels } = loadCSV(
  'kc_house_data.csv',
  {
    shuffle: true,
    splitTest: 10,
    dataColumns: ['lat', 'long', 'sqft_lot', 'sqft_living'],
    labelColumns: ['price']
  }
);

features = tf.tensor(features);
labels = tf.tensor(labels);

testFeatures.forEach((testPoint, i) => {
  const result = knn(features, labels, tf.tensor(testPoint), 10);
  const err = (testLabels[i][0] - result) / testLabels[i][0];
  console.log('Error', err * 100);
});


'스터디 > Tensorflow JS' 카테고리의 다른 글

tensorflow js 입문 (스터디 3주차 정리)  (0) 2018.12.05

NodeJS 스터디 1주차 정리

부산에서 매주 진행되는 노드 입문 강좌 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

이 강좌는 제로초님의 인프런 강좌를 보고 요약해놓은 내용입니다.

  • 챕터 2 - ES2018
  • 챕터 3 - 노드 기능 알아보기

const & let

const

const 는 객체가 할당된 경우 변경이 불가능하지만 오브젝트에 대한 내부 값은 변경가능하다.

const item = {a: 1, b: 2, c: 3};

item.a = 3;

item => {a: 3, b: 2, c: 3}

let

구문적인 변수 영역 규칙을 지원한다.

{} 블록내에 변수 영역만 처리된다.

예를 들어 보자.

var 로 할 경우 밖의 변수까지 변경되는 걸 볼수 있다.

var test = "test";

if (test) {
    var test = "test2";
    console.log('블록내 : ',test);
}

console.log('블록밖', test);

.....
블록내 : test2
블록밖 test2

하지만 let 으로 할 경우 글로벌 변수의 값을 보호할 수 있다.

var test = "test";

if (test) {
    let test = "test2";
    console.log('블록내 : ',test);
}

console.log('블록밖 : ', test);

......
블록내 :  test2
블록밖 :  test

템플릿 문자열

템플릿 문자열은 문자열 연결 대신 사용할 수 있다. 중간에 변수를 삽입할수 있다.

${변수}, ` 로 감싼다.

console.log(test + " " + test2); //기존 방식

console.log(`${test} ${test2}); //템플릿 문자열

객체 리터럴

화살표 함수 (중요)

화살표를 사용하면 모든 함수 정의를 한줄로 끝낼수 있다. function 키워드를 없앴고, 화살표가 어떤 값을 반환하는지 지정해주기 때문에 return 도 없다. 만약 파라미터가 하나만 받는 경우 괄호도 생략이 가능하다.

화살표 함수는 this를 새로 바인딩 하지 않는다. 예를 들어 다음 코드에서 this는 city객체가 아니다.

const city = {
    areas: ["서울","부산"],
    print: function(delay = 1000) {
        setTimeout(function() {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

....error
Cannot read property 'join' of undefined

이 경우 this는 window 객체이기 때문에 city 는 undefined 이다.

화살표 함수는 함수 내부의 this를 외부의 this와 같게 만든다.

const city = {
    areas: ["서울","부산"],
    print: (delay = 1000) => {
        setTimeout( () => {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

....error
Cannot read property 'join' of undefined

위 코드에서 this도 결국에는 window 의 객체이기 때문에 오류가 난다.

그래서 상단의 print 함수를 function 으로 만들어주면 오류가 발생되지 않는다.

const city = {
    areas: ["서울","부산"],
    print: function(delay = 1000){
        setTimeout( () => {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

.....error
서울,부산

구조분해 ( destructuring )

구조분해를 사용하면 객체 안에 있는 필드 값을 원하는 변수에 대입할 수 있다.

비구조화 할당 시 this가 의도와 다르게 동작하는 현상이 있을수 있는 점 유의하자.

var city = {
    area: ['busan','seoul'],
    latlng: ['123:456', '789:321']
}

const { area, latlng } = city;

console.log(area[0], latlng[0]);
...

const [ busan, seoul ] = area;

console.log( busan, seoul );

위 코드 처럼 배열도 비구조화 할당이 가능하다.

스프레드 연산자

기존 배열의 값은 변경하지 않고 새로 만들어서 리턴한다.

파라미터 갯수를 동적으로 받을수 있다.

const x = (a, ...b) => console.log(a,b);

x(1,2,3,4,5);

........
1 [ 2, 3, 4, 5 ]

Promise

비동기적인 동작을 잘 다루기 위한 방법 중 하나이다.

const promise = new Promise((resolve, reject) => {
    const a = 1;
    const b = 1;
    if(a + b <= 2) {
        resolve(a + b);
    } else {
        reject(a + b);
    }
});

promise
    .then(( success ) => {console.log(success)})
    .catch((error) => {console.log(error)});


.... 성공
> 2

또한 여러개의 then으로 사용가능하다. 콜백안에 다시 콜백이 있을 경우 여러개의 then 으로 이어진다. 그러기 위해선 resolve로 리턴을 해야줘야 한다.

const promise = new Promise((resolve, reject) => {
    const a = 1;
    const b = 1;
    if(a + b <= 2) {
        resolve(a + b);
    } else {
        reject(a + b);
    }
});

promise
    .then(( success ) => {
        return new Promise((resolve, reject) => {
            resolve("result : " + success);
        })
    })
    .then((success2) => console.log(success2))
    .catch((error) => {console.log("reject:" + error)});

....
result : 2

Promise API

무조건 성공 또는 실패 하는 경우일 경우 더 줄여서 가능하다.

const success = Promise.resolve('success');    
const failed = Promise.reject('failed');

Promise 함수들을 모아서 하나의 Promise 로 실행할수 있다.

전부 성공시 success 로 넘어가지만 한개라도 실패시 catch 로 들어가는 점 유의하자.

Promise.all([promise, success])
    .then((results) => {
        console.log(results);
    })
    .catch((error) => {
        console.log(error);
    })

Async / Await

Async 도 결국 내부에는 Promise 기반이므로 Promise를 꼭 습득하고 가자.

Promise의 단점은 결국 콜백함수내에서 이루어지기 때문에 외부 코드들과 연동성이 비동기로 이루어진다. 그래서 그걸 극복하게 동기형으로 바꿔주는 게 Async/Await 이다.

check = async () => {
    console.log('await result: ',await promise);
}

check();

오류 처리는 try / catch 문으로 한다.

check = async () => {
    try {
        console.log('await result: ',await promise);
    } catch(e) {
        console.error('error: ',e)
    }
}

check();

모듈

자바스크립트 모듈은 자바스크립트 파일에서 쉽게 불러와서 활용할 수 있는 재사용 가능한 코드 조각들을 말한다. 즉 쪼개서 불러와서 사용이 가능하다는 것이다.

자바스크립트는 각각의 모듈을 별도의 파일로 저장한다. 모듈을 만들고 외부에 익스포트 하는 방법에는 한 모듈에서 여러 자바스크립트 객체를 외부에 노출시키는 방법과 모듈당 하나의 자바스크립트 객체를 노출시키는 방법이 있다.

여러 객체를 외부에 노출 시키는 방법

>> helper.js

export const print = (message) => log(message, new Date())

export const log = (message, timestamp) => console.log(`${timestamp.toString()}: ${message}`)

module.exports = {
    print,
    log
}
const { print } = require('./txt_helper');

print("good");

하나의 객체를 외부에 노출 시키는 방법

const print = (message) => console.log(message, new Date())

export default print;
import print from './txt_helper_only_one.mjs';

print("good");

global

노드의 전역 객체

노드 실행환경내 전역에서 공통적으로 사용되는 객체이다.

> B.js

module.exports = () => global.message;
> C.js

const B = require('./B');

global.message = 'ok';

console.log(B());

>> ok 출력

console

console 객체네는 디버깅을 도와주는 많은 메소드가 준비되어 있다.

  • console.time('인자') & console.timeEnd('인자') - 전체 걸리는 시간을 측정해준다. (인자는 똑같아야 한다)
  • dir: 오브젝트 객체를 로깅할때 사용한다. colors 는 색상 지정, depth 는 깊이 지정
  • trace: 에러 발생시 오류 경로를 추척해서 보여준다.

setTimeout, setInterval, setImmediate

https://nodejs.org/ko/docs/guides/timers-in-node/

  • setTimeout : 일정시간 후에 콜백이 실행된다.
    • clearTimeout 로 취소 가능
  • setInterval : 일정 시간마다 콜백이 실행된다.
    • clearInterval로 취소 가능
  • setImmediate : 이벤트 루프 주기 끝에 코드를 실행한다.
    • clearImmediate로 취소 가능

__filename, __dirname

  • __filename 은 현재 파일 위치까지 위치를 리턴한다.
  • __dirname 은 디렉토리까지 위치를 리턴한다.

process

현재 실행중인 노드 프로그램 정보가 들어있다.

  • process.version : 노드 설치 버전
  • process.arch : 32비트인지 64비트인지 체크
  • process.platform : 운영체제
  • process.pid : 프로세스 아이디 ( 문제가 발생시 강제 종료시 아이디 지정 가능 )
  • process.uptime : 프로세스 실행되고 얼마나 지났는지 체크
  • process.cwd : 노드 프로그램이 실행되고 있는 위치
  • process.exePath : 노드 설치 위치
  • process.cpuUsage : cpu 사용량
  • process.exit : 프로세스 종료

OS

운영체제 관련된 모듈

  • os.cpu 의 경우는 멀티 프로세싱에서 자주 쓰인다.

Path

경로 관련된 모듈. **중요한 모듈중 하나이다. **

  • path.delimiter : 환경변수 패스 출력
  • path.sep : 경로 구분자 표시 . 윈도우는 \\ 이다.
  • path.dirname : 경로 폴더
  • path.extname : 확장자
  • path.basename : 파일 이름
  • path.parse : 구조로 분해
  • path.format : 분해된 구조를 합쳐준다.
  • path.normalize : 잘못된 경로를 자동으로 정상적으로 만들어준다.
  • path.isAbsoulte : 경로가 절대경로인지 상대경로인지 체크
  • path.join : 절대 경로 무시하고 조각나있는 경로를 합쳐준다.
  • path.resolve : 절대 경로 고려해서 합친다. 루트는 C:\

Url

도메인이 생략된 경우는 parse 를 통해서 처리

  • parse
  • format
  • searchParams : 파라미터 내용을 오브젝트 형태로 가져온다.
    • 파라미터들을 추가,수정,삭제등 할수 있다. (append,set,delete,get)

url 구조� �� �미� ��결과

querystring

  • url 모듈과 같이 사용한다.
  • url.parse ->querystring.parse 로 이용
  • stringify 는 파싱된 걸 다시 합쳐준다.

crypto

단방향 암호화(해시)
  • 비밀번호를 암호화할때 필요하다.

  • 복호화는 안된다.

  • createHash 는 비밀번호가 짧을 수록 같은 값이 나올수 있다.

  • 좀 더 나은 암호화 방식인 pbkdf2 함수를 사용

    • salt 라는 문자열을 비밀번호에 추가해서 좀 더 암호화한다.
    • salt 는 암호화된 비밀번호와 같이 저장
    • iteration 은 1초정도가 걸릴때까지 올려주면 좋음
  • 실무에선 bcrypt 등이 사용된다.

crypto.createHash('sha512').update('password').digest('base64');
양방향 암호화 (복호화 가능)
  • 암호화시 createCipher 함수 사용
  • aes-256 또는 aes-256-cbc 를 많이 사용한다.
  • 복호화시 createDecipher 함수 사용
  • key 값을 알아야 복호화를 할 수 있다.
  • update 함수 사용시 base64,utf8 인자가 암호화와 복호화시 바뀐다.

Util

  • callbackify : promise 함수를 거꾸로 callback 함수로 바꿔준다.
  • promisifycallback 함수를 promise 로 바꿔주는 유틸 함수 (중요함)
  • deprecate : 폐기되는 함수임을 지정한다.
    • 오류 메세지도 인자로 넣을 수 있다.
    • 결과에 Warning 이 나온다.

Fs (File System)

  • 비동기 방식으로 콜백으로 결과로 받는다.
  • 여러개의 콜백을 지정시 순서가 일정하지가 않다.
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
    if(err) {
        throw err;
    }

    console.log(data); //바이너리 파일로 리턴된다. 
    console.log(data.toString());
});

.........
<Buffer ed 85 8c ec 8a a4 ed 8a b8 31 0d 0a ed 85 8c ec 8a a4 ed 8a b8 32>
테스트1
테스트2
  • 동기 방식으로는 sync 방식도 제공한다.
    • readFileSync

기타 함수

  • access
  • mkdir
  • open
  • rename
  • unlink : 파일 삭제
  • rmdir : 폴더 삭제

promise

  • 노드 10에서 promise사용가능

버퍼 ( Buffer ), 스트림 ( Stream )

  • 데이터를 보낼때 일정량의 저장 공간에(버퍼) 담아서 보낸다.
  • 버퍼들의 통로를 뜻함
const fs = require('fs');
const readStream = fs.createReadStream('./readme.txt', { highWaterMark: 8});
const data = [];

readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data', chunk, chunk.length);
});

readStream.on('end', () => {
    console.log('end', Buffer.concat(data).toString());
});

readStream.on('error', (error) => {
    console.log('error', error);
})
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8

청크별로 날라와서 end 이벤트 등록시 그 버퍼를 합쳐서 하나의 완전품으로 만든다.

WriteStream

const fs = require('fs');

const writeStream = fs.createWriteStream('./readme.txt');
writeStream.on('end', () => {
    console.log('완료')
})
writeStream.write('쓰기 완료1');
writeStream.write('두번째 줄');
writeStream.end();

Pipe

내부적으로 읽기와 쓰기를 연결해준다.

  • copy 함수가 따로 새로 만들어져서 사용가능하다.

zlib

압축을 할수 있다.

Event

미리 이벤트를 만들어 놓고 어떤 행동 발생시 콜백이 발생한다.

  • addListener
  • on : addListener 와 같지만 여러번 등록가능하다.
  • once : 한번만 실행된다.
  • listenerCount : 이벤트가 몇개 달려 있는지 갯수 확인
const EventEmitter = require('events');
const myEvent = new EventEmitter();

myEvent.addListener('visit', () => {
    console.log('welcome');    
});

myEvent.on('exit', () => {
    console.log('good bye');
});

myEvent.on('exit', () => {
    console.log('good bye please');
});

myEvent.once('special', () => {
    console.log('special event')
});

myEvent.emit('visit');
myEvent.emit('exit');
myEvent.emit('special');
myEvent.emit('special');

..........
welcome
good bye
good bye please
special event <-- 한번만 실행된걸 볼 수 있다. 

이벤트 취소

  • removeAllListeners(이름) 여러번 등록 했을 경우 다 제거
  • removeListener(이름, callback) 하나만 제거 가능하다. 어떤 걸 제거 해야 할지 알수 없으므로 콜백을 지정

예외처리

  • 노드는 싱글 쓰레드이므로 오류 발생시 전체에 영향을 끼칠수 있으므로 신중하게 처리
  • throw new Error
  • try / catch 로 처리 가능
  • async / await 도 try / catch
  • 내장 함수들은 error 발생해도 시스템이 중지되진 않는다.
  • process.on 으로 시스템 에러가 발생시 중지가 되지 않게 할 수 있다.
    • uncaughtExcepetion 으로 모든 에러를 잡을수 있다.
    • process.on 이벤트 실행이 꼭 보장되지 않는다.


이상으로 1주차 노드 스터디 내용 정리사항이었습니다. 

참석해주셔서 감사합니다. 

Tensorflow JS 입문 ( 부산 3주차 스터디 )

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/machine-learning-with-javascript 을 같이 보면서 공부중입니다.

목표

  • 텐서플로 JS 에 대한 이해
  • 텐서플로를 이용한 예제 재설계 ( lodash -> tensorflow)
  • tensorflow 를 이용해서 knn 알고리즘 재설계
  • tensorflow와 함계 다른 알고리즘 빌딩

저번주까지(2주차) 우리는 기본적인 머신러닝 기법인 KNN 에 대해서 공부했다.

공부한 내용은 다음과 같다.

1543958445494

공식 홈페이지

https://js.tensorflow.org/

1543959044497

첫번째 예제

tensorflow js를 이용해서 배열에 있는 숫자들을 작업하자

1543959200980

Dimensions

  • 1차원, 2차원, 3차원...n차원 배열

1543959388735

Shape

  • 각각의 배열에 얼마나 많은 레코드를 가지고 있는지 표현
  • 배열에 대한 길이를 뜻함

예를 들어 [5,10,17] 에 대한 shape 는 3 이라고 생각하면 된다.

1543959771631

1543959631283

1543959690992

1543959728153

실습

https://stephengrider.github.io/JSPlaygrounds/

const data = tf.tensor([1,2,3])

data.shape

>> [3]
const data = tf.tensor([1,2,3])
const otherdata = tf.tensor([4,5,6]);

data.add(otherdata);

>> [5,7,9]

1543960433839

const data = tf.tensor([1,2,3])
const otherdata = tf.tensor([4,5,6]);

data.sub(otherdata);
data.mul(otherdata);


>> [-