아래와 같이 중첩된 값이 있다면, 각각의 단계별로 객체가 존재하나 여부를 확인하고 값을 꺼내는 코드를 작성해야 합니다. 옵셔널 체이닝 없이 꺼낸다면, 코드가 길어지고, 무엇보다 직관적이지 못합니다.

<script type="module">

    const some = {
        deeply:{
            nested:{
                value:99
            }
        },
        dialog:{
            say:"hello"
        }
    };

    const x = some && some.deeply && some.deeply.nested && some.deeply.nested.value;
    const y = some && some.dialog && some.dialog.say && some.dialog.say;
    console.log(x, y);

</script>

 

하지만 옵셔널 체이닝을 사용하면, 아래와 같이 코드가 간결해 지며, 중간에 null 이나 undefine을 만나면 undefine을 반환하고 코드를 수행하지 않습니다.

<script type="module">

    const some = {
        deeply:{
            nested:{
                value:99
            }
        },
        dialog:{
            say:"hello"
        }
    };

    const x = some?.deeply?.nested?.value;
    const y = some?.dialog?.say;
    console.log(x, y);

</script>

 

 

 

'JavaScript' 카테고리의 다른 글

javascript, 디스트럭처링(destructuring)  (0) 2023.01.01
javascript, for-in, for-of  (0) 2023.01.01
javascript, var, let, const  (0) 2023.01.01
html css 7가지 단위  (0) 2020.08.21
Ajax 크로스 도메인  (0) 2017.10.29
Posted by 창업닉군
,

디스트럭처링은 객체나 배열에서 값을 쉽게 가지고 오는 방법입니다. 디스트럭처링이 없을 때는 객체나 배열에서 값을 가지고 올 때 다소 코드가 길었지만, 아래 예들 처럼 디스트럭처링을 사용한다면, 코드가 상당히 간결해 질 것 입니다.

 

객체

기존에는 아래와 같이 사용해 값들을 가지고 왔습니다.

<script>

    const hotel = {
        door:3,
        light:6
    };

    const door = hotel.door;
    const light = hotel.light;

    console.log(door, light);

</script>

 

하지만 아래와 같이 디스트럭처링을 하면 길이가 상당히 짧아 집니다. 아래 처럼 변수를 또 나눌 일은 없지만, 함수에서 결과로 객체를 받을 경우에 사용한다면 상당히 효과적입니다.

<script type="module">

    const hotel = {
        door:3,
        light:6
    };

    const {door, light} = hotel;
    console.log(door, light);

</script>

 

 

배열

기존의 방법

<script type="module">

    const soldier = ['jake', 'jun', 'jane', 'Paul'];

    const soldier_0 = soldier[0];
    const soldier_2 = soldier[2];
    console.log(soldier_0, soldier_2);

</script>

 

디스트럭쳐

<script type="module">

    const soldier = ['jake', 'jun', 'jane', 'Paul'];
    const [soldier_0, , soldier_2,] = soldier;
    console.log(soldier_0, soldier_2);

</script>

 

중첩된 디스트럭쳐

객체 속에 배열이 있는 경우 아래와 같은 방법으로 가지고 올 수 있습니다.

<script type="module">

    const jade = {
        mountain:["한라산","백두산"],
        river:["한강","낙동강"]
    };
    const {mountain:[choice_m],river:[,river_nak]} = jade;
    console.log(choice_m, river_nak);

</script>

 

 

 

디스트럭처와 스프래드는 궁합이 좋으므로, 같이 사용하면 매우 간결한 코딩이 가능합니다.

'JavaScript' 카테고리의 다른 글

javascript, optional, chaining  (0) 2023.01.01
javascript, for-in, for-of  (0) 2023.01.01
javascript, var, let, const  (0) 2023.01.01
html css 7가지 단위  (0) 2020.08.21
Ajax 크로스 도메인  (0) 2017.10.29
Posted by 창업닉군
,

for-of : 배열의 값을 반복해 줍니다.

for-in : 객체의 키값으로 반복을 해 줍니다.

아래 소스로 차이를 느껴보세요.

<script>

    const one = [6,8,2,4,0];
    for( let o of one ){
        console.log('o', o);
    }

    const two = {'과일':'사과','나라':'미쿡','언어':'바디랭귀지'};
    for( let key in two ){
        console.log( key, two[key] );
    }

</script>

 

'JavaScript' 카테고리의 다른 글

javascript, optional, chaining  (0) 2023.01.01
javascript, 디스트럭처링(destructuring)  (0) 2023.01.01
javascript, var, let, const  (0) 2023.01.01
html css 7가지 단위  (0) 2020.08.21
Ajax 크로스 도메인  (0) 2017.10.29
Posted by 창업닉군
,

javascript는 다른 언어와 다르게 선언하는 방법이 var 하나만 있었습니다. c++, c# 처럼 형식을 정의하지 않았고 변수로 바로 사용하기에 하나만 밖에 필요가 없었습니다. 하지만 javascript는 눈부시게 발전해 복잡하고, 기능이 다양해 지며 아주 많은 문제를 만들어 냈습니다. 특히 promise 전까지 함수 중첩 지옥에서는 전역변수인 var는 정말 오류를 만들기 쉬웠습니다. 이를 극복하기 위해 ES2015에서는 let과 const가 도입됩니다.

 

var, let

var는 선언된 바깥 스코프에서도 사용이 가능합니다. 같은 function 속이 scope 입니다. let 을 실행하면 외부에서 사용이 불가능합니다. try-catch 문 같이 try 부분에 사용된 변수중 catch로 전달될 값이 있다면, var 그 외에 일반적인 경우 let을 사용하면 좋을 듯 합니다. 아래의 코드를 실행해 보세요.

<script>

    for(var i=0;i<10;i++){

    }
    console.log("i", i);

    for(let u=0;u<9;u++){

    }
    console.log("u", u);

</script>

 

const

const는 상수를 정의할 때 사용합니다. javascript같은 경우 언어가 유연해, override 되기 쉬온데, cosnt를 사용하면 그럴 우려는 없습니다. 하지만, const 내부의 값들은 변경이 가능합니다. 아래의 코드를 실행해 차이를 느껴보세요.

<script>

    let spr = {in:1};
    console.log('spr', spr);

    spr.in += 4;
    console.log('spr', spr);

    spr = {in:2};
    console.log('spr', spr);



    const ho = {out:1};
    console.log('ho', ho);

    ho.out+= 6;
    console.log('ho', ho);

    ho = {out:3};
    console.log('ho', ho);

</script>

 

 

 

 

 

 

'JavaScript' 카테고리의 다른 글

javascript, optional, chaining  (0) 2023.01.01
javascript, 디스트럭처링(destructuring)  (0) 2023.01.01
javascript, for-in, for-of  (0) 2023.01.01
html css 7가지 단위  (0) 2020.08.21
Ajax 크로스 도메인  (0) 2017.10.29
Posted by 창업닉군
,

WebSockets 설치

파이썬을 구현하기 위해서는 먼저, python과 pip로 websockets 를 설치하셔야합니다.

웹소켓 설치
$ pip install websockets

설치확인
$ pip list

 

웹소켓 서버 코딩

너무 간단해서 사실 놀랐습니다. websockets.serve 함수에 인자로 각각 '실행메소드', 'ip 혹은 주소', 'port'를 넣어주고, 응답을 대기하는 동안 종료되지 않도록. asyncio.get_event_loop().run_until_complete, asyncio.get_event_loop().run_forever를 이용해 실행해 주시면되겠습니다.

서버는 복잡하지 않도록 응답을 그대로 돌려 주는 에코 서버로 만들었습니다. 소스는 아래와 같습니다.

import asyncio;
import websockets;

port = 10000

async def accept(self, path):
    while True:
        data = await self.recv()
        print("receive:" + data)
        send_str = 'echo : ' + data
        await self.send(send_str)
        print('send => ' + send_str)

print("웹소켓 시작 포트넘버:" + str(port))
start_server = websockets.serve(accept, "localhost", port)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
print("End Service!")

 

 

클라이언트 코딩

간단히 html과 javascript를 통해 예제를 만들어 봤습니다. 대체로, websocket.onopen, websocket.onclose 등의 함수를 사용하지만 저는 괄호가 중첩되는 것이 싫어 이벤트 형식으로 만들었습니다.

<title>WebSocket 테스트</title>
<style>
    .spacer {margin-bottom: 1rem;}
</style>

<div class="spacer">
    <span>상태 : </span><span id="status">접속안됨</span>
</div>
<div id="recived" class="spacer" style="height: 15rem; border: #aaa 1px solid; padding: 0.5rem; max-width: 30rem;"></div>
<div class="spacer">
    <input id="message" />
    <button id="send">보내기</button>
    <button id="close">종료</button>
</div>

<script>

    const url = 'ws://localhost:10000';
    const ws = new WebSocket(url);

    const E_Receive = document.querySelector('#recived');
    const E_Message = document.querySelector('#message');
    const E_Status = document.querySelector('#status');
    const E_Btn_Send = document.querySelector('#send');
    const E_Btn_Close = document.querySelector('#close');

    // 웹소켓 이벤트 처리.
    ws.addEventListener("open", e=>{
        E_Status.innerHTML = "접속";
    });
    ws.addEventListener("message", e=>{
        console.log("e",e);
        E_Receive.innerHTML += `<div>수신 : ${e.data}</div>`;
    })

    // ui 이벤트 처리.
    E_Btn_Send.addEventListener('click',e=>{
        if(!E_Message.value) return alert("메세지를 입력하지 않았습니다.");
        E_Receive.innerHTML += `<div>발신 : ${E_Message.value}</div>`;
        ws.send(E_Message.value);
        E_Message.value = "";
    })

    E_Btn_Close.addEventListener('click',e=>{
        ws.close();
        E_Status.innerHTML = "종료";
    });

</script>

 

 

그럼 즐거운 코딩 되시길 바랍니다.

 

 

 

 

 

'파이썬' 카테고리의 다른 글

window 장고설치  (0) 2023.01.02
Posted by 창업닉군
,

웹페이지에 맵을 넣을 일이 있어, 구글맵을 적용하고, 잊지 않기 위해 글을 남겨 둡니다.

 

[공식문서]

Maps JavaScript API : https://developers.google.com/maps/documentation/javascript/tutorial .

 

[참고 사이트]

Google Maps Platform : https://cloud.google.com/maps-platform .

 

 

1. API키 받기.

먼저 구글 지도 플랫폼으로 들어 갑니다. 그리고 오른쪽 상단의 시작하기 버튼을 눌러 줍니다.

 

그리고 사용할 지도 플랫폼을 지정합니다. 일단 지도만 있으면 될것 같지만, 지역정보도 함께 선택해 둡니다.

 

그리고 새로운 프로젝트를 생성합니다. (기존의 프로젝트도 사용해도 상관은 없습니다.)

 

여기까지 들어 오면, 생성한 프로젝트에 결제 사용을 설정할 수 있습니다. 결제 계정을 만들어 줍니다.

 

결제계정을 지정하였다면 아래와 같이 프로젝트가 설정되고, 지도 사용을 위한 API 키가 나옵니다.

 

2. 소스 작성.

소스 작성은 공식 문서 Maps JavaScript API 에 있는 코드를 그대로 복사해 사용하면 됩니다.api키만 바꾸시면됩니다.

<!DOCTYPE html>
<html>
  <head>
    <title>Simple Map</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <style>
      /* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
      #map {
        height: 100%;
      }
      /* Optional: Makes the sample page fill the window. */
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      var map;
      function initMap() {
        map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: -34.397, lng: 150.644},
          zoom: 8
        });
      }
    </script>
    <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
    async defer></script>
  </body>
</html>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Posted by 창업닉군
,

HTML5와 웹소켓이 나오기 전까지, 홈페이지는 그저 요청에 대한 서버 결과를 돌려 주는 형태로만 제작이 가능했습니다. 물론 여러가지 방법을 통해, 비슷한 형태로 제작이 가능했지만, 근본적인 대책은 아니였습니다. TCP/IP 통신을 하지 못해, 챗팅 게임등의 서버로 사용하기 힘들었지만, 웹소켓이 나온 이후로는 매우 편리해 졌습니다.

거기다, 쉬운 방식으로 구현히 가능해, 적어도 한 시간 이내에 기능을 구현하는 것이 가능합니다. 제가 TCP, UDP통신을 모두 구현해 보았지만, 웹소켓만큼 구현히 쉽지는 않았습니다.

이 문서에는 javascript를 통한, 클라이언트를 구현해 보도록 하겠습니다. websocket.org 라는 사이트에서 웹소켓 에코 서버를 지원해 주므로, 클라이언트 부터 만들고, 이 클라이언트를 바탕으로 서버를 만들면 서버 또한 깔금하게 만들수 있습니다.

 

[공식문서]

공식 사이트 : websocket org : https://www.websocket.org .

 

 

1. 샘플 보기 및 샘플 테스트.

공식 사이트에 가면, 샘플에 대한 구현 방법과 에코서버를 어떻게 테스트 하는지 잘 설명하고 있습니다.

먼저, 공식 사이트로 이동합니다. ( 링크 ) 그럼, 아래와 같이 페이지가 나오는데 화면 중앙의 GET STARTED 버튼을 눌러 주도록 합니다.

그럼 에코 테스트 페이지가 나옵니다. 화면 중앙의 녹색 This browser supports WebSocket 이라는 문구는, 여러분이 사용하는 브라우저가 웹소켓을 지원하고 있음을 알려주고 있습니다. 요즘은 거의 모든 브라우저가 웹소켓을 지원하지만, 얼마전까지만 해도 그렇지 않았습니다.

원할한 서비스를 위해 여러분도 여려분의 홈페이지에 방문자의 브라우저가 웹소켓을 지원하는지 여부를 알려줄 필요가 있을 지 모릅니다. (구현은 간단합니다.) location: 부분에 입력된 값은 테스트를 위한 에코서버의 주소 입니다. 일단 그대로 두고, 컨넥트 버튼을 눌러 보도록 하겠습니다.

그럼 에코 서버와 연결이 되어 Log에, CONNECTED라고 표시됩니다.

메시지 부분에 적당한 메시지를 입력하고, Send를 눌러 보도록 하겠습니다.

그럼 아래와 같이 보낸 메시지를 서버가 그대로 돌려 줍니다.

마지막으로 서버와의 접속을 끊어주는 Disconnect 를 눌러 주도록 합니다.

그럼 아래와 같이 DISCONNECTED 라는 로그가 남기며 접속이 종료됩니다.

간단한 기능이지만, 챗팅 및 게임을 구현하기에 충분한 역할을 하고 있는 것을 확인할 수 있습니다. 하지만, 이렇게 구현하는데, 코드 몇 줄으면 끝이난다는 것이 더 매력적입니다.

 

 

 

2. 소스 보기.

위의 페이지에서 아래로 스크로를 해 보면, 위 코드에 대해서 간단히 구현해 놓은 코드를 제공합니다.

워낙 소스가 잘되어 있어서, 따로 무언가를 할 필요가 없어서, 공식예제에 주석만 달아 아래에 소스를 공개합니다.

  <!DOCTYPE html>
  <meta charset="utf-8" />
  <title>WebSocket Test</title>
  <script language="javascript" type="text/javascript">

  var wsUri = "wss://echo.websocket.org/";
  var output;

  // DOM 이 형성되었을 때 가장 먼저 호출되는 메소드 입니다.
  function init()
  {
    output = document.getElementById("output");
    testWebSocket();
  }

  // 서버와의 접속을 시작하는 메소드 입니다.
  function testWebSocket()
  {
  
    // 지정한 주소로 웹소켓 연결을 시도하는 코드 입니다.
    websocket = new WebSocket(wsUri);
    
    // 웹소켓의 각 이벤트에 대한 처리 메소드들을 연결한 코드들 입니다.
    // 위의 호출 이후 이벤트를 연결해도 문제가 없는 이유는 위의 웹소켓은 콜백 처리를 하게되는데,
    // 콜백으로 호출되는 메소드들은 무조건 다음 프레임에 처리됩니다. 그래서 뒤에 호출해도 됩니다.
    websocket.onopen = function(evt) { onOpen(evt) };
    websocket.onclose = function(evt) { onClose(evt) };
    websocket.onmessage = function(evt) { onMessage(evt) };
    websocket.onerror = function(evt) { onError(evt) };
    
    
  }

  // 서버와 접속이 완료되었을 때 호출되는 메소드 입니다.
  function onOpen(evt)
  {
    writeToScreen("CONNECTED");
    doSend("WebSocket rocks");
  }

  // 어떤 이유로든 서버와의 접속이 종료되었을 때, 호출되는 메소드 입니다.
  function onClose(evt)
  {
    writeToScreen("DISCONNECTED");
  }

  // 메시지를 받았을 때, 처리하는 메소드 입니다.
  function onMessage(evt)
  {
    writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
    websocket.close();
  }

  // 에러가 발생했을 때, 처리 메소드 입니다.
  function onError(evt)
  {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
  }

  // 서버로 메시지를 보냅니다.
  function doSend(message)
  {
    writeToScreen("SENT: " + message);
    websocket.send(message);
  }

  // 로그에 메시지를 남겨 줍니다.
  function writeToScreen(message)
  {
    var pre = document.createElement("p");
    pre.style.wordWrap = "break-word";
    pre.innerHTML = message;
    output.appendChild(pre);
  }

  // 브라우저의, DOM 구성이 완료되면, 가장 먼저 실행할 메소드를 지정하는 메소드 입니다.
  // (즉, 가장 먼저 실행될 메소드 지정).
  window.addEventListener("load", init, false);

  </script>

  <h2>WebSocket Test</h2>

  <div id="output"></div>

 

 

 

 

 

 

 

 

 

 

 

Posted by 창업닉군
,

android webview, javascript 통신


1. 브릿지 만들기.

별것은 없다. 실제 안드로이드와 연동동작을 할 클래스들을 따로 모아두었다 생각하면된다. 다만 클래스 중 javascript에서 호출될 메소드는 @JavascriptInterface 어노테이션을 붙혀주면된다. 아래는 예제 소스다. (로그를 적어주는 메소드).

public class AndroidBridge {

private String TAG = "AndroidBridge";

// 로그를 띄우는 메소드 입니다.
@JavascriptInterface
public void call_log( final String _message ){

Log.d(TAG, _message);

}


}


2. 브릿지 셋팅하기.

MainActivity 클래스,  onCreate 메소드 적당한 곳에, 적당히 셋팅해 주면된다. (그냥 클래스를 생성하고, 셋팅하는 것 뿐이다.)

AndroidBridge ab = new AndroidBridge(wv, MainActivity.this );
wv.addJavascriptInterface( ab, "Android" );

셋팅하는 addJavascriptInterface 메소드의 첫 인자는 브릿지 클래스를 바탕으로 생성한 객체, 2번째는 javascript의 어떤 객체속에 인터페이스 메소드들을 넣어줄까를 결정하는 값이다. 아래 호출 부분을 보면 쉽게 이해가 가리라 생각된다.



3. javascript 호출부분.

그냥 호출하면된다. 아래와 같이.

<javascript>

   window.Android.call_log('원하는 메시지');

</javascript>



4. 안드로이드 에서 javascript로 메시지 보내기.

웹의 a 태그 href 에서 javascript를 실행할 때, <a href="javascript:alert('안녕');"> 등의 방법으로 간단히 자바 스크립트를 실행할 수 있을 것이다. 똑 같이  webview.loadUrl("javascript:alert('안녕');") 이라고 전달해 준다면, 똑 같은 동작을 한다.
다만, Webview와 Android는 비동기 상태 이므로 쓰레드로 넣어전달해 주어야 한다.

주의 할점. Handler를 넣으려고 하면, 자동완성 순서상,  import java.util.logging.Handler 가 등록되며, 되지 않을 수 있다. 꼭, import android.os.Handler를 포함시키자.

javascript 속 메소드를 호출하는 코드가 있는, 클래스에 아래와 같이 Handler를 추가해 주도록 합니다.

final public Handler handler = new Handler();

그리고 적당한 곳에 아래와 같이 자바 스크립트 함수를 호출하면됩니다.

handler.post(new Runnable() {
@Override
public void run() {
mAppView.loadUrl("javascript:alert('안녕')");
}
});


5. 전체 소스

>> MainActivity.java


import android.app.Activity;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebChromeClient;
import android.webkit.WebView;

public class MainActivity extends AppCompatActivity {

private WebView wv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Toolbar toolbar = findViewById(R.id.toolbar);
// setSupportActionBar(toolbar);



// 웹뷰 컨트롤러를 가지고 옵니다.
wv=(WebView)findViewById(R.id.activity_main_webview);



// 웹뷰에 자바 스크립트를 사용할 수 있게 허용.
wv.getSettings().setJavaScriptEnabled(true);

// 각종 알림 및 요청을 받게되는 WebViewClient를 설정합니다. (직접만든 웹뷰 클라이언트를 상속한 클래스 설정)
wv.setWebViewClient(new CustomWebViewClient());

// 크롬 핸들러 설정. 이 핸들러는 자바 스크립트 대화상자, favicon, 제목과 진행상황 처리에 사용하기 위해.
// WebChromeClient를 구현한 것 입니다.
wv.setWebChromeClient( new WebChromeClient());



// 웹뷰에 첫 주소를 연결하도록 합니다.
String first_addr = getString(R.string.first_addr);
wv.loadUrl(first_addr);



// FloatingActionButton fab = findViewById(R.id.fab);
// fab.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// .setAction("Action", null).show();
// }
// });



// Javascript 브릿지를 사용하도록 합니다.
AndroidBridge ab = new AndroidBridge(wv, MainActivity.this );
wv.addJavascriptInterface( ab, "Android" );


}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);




return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}




>> AndroidBridge.java


import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;

import android.os.Handler;


public class AndroidBridge {

private String TAG = "AndroidBridge";
final public Handler handler = new Handler();

// 새성시 내부적으로 사용할 코드들을 저장합니다.
private WebView mAppView;
private MainActivity mContext;

// 기본 생성자 입니다.
public AndroidBridge(WebView _mAppView, MainActivity _mContext)
{

// 입력받은 값들을 저장하도록 합니다.
mAppView = _mAppView;
mContext = _mContext;

}

// 로그를 띄우는 메소드 입니다.
@JavascriptInterface
public void call_log( final String _message ){

Log.d(TAG, _message);

handler.post(new Runnable() {
@Override
public void run() {
mAppView.loadUrl("javascript:alert('["+ _message +"] 라고 로그를 남겼습니다.')");
}
});



}


}

>> 웹페이지 파일.

<html>

<head>

<title>일단 웹뷰</title>



</head>



<body>

일단 웹뷰를 표시하도록 합니다.!!!

<a href="javascript:location.reload(true);">새로고침</a>
<a href="javascript:window.Android.call_log('연동테스트');">연동</a>

</body>



</html>




그럼.. 이만..







Posted by 창업닉군
,