한글 낱말퍼즐, 단어 십자퍼즐 게임입니다.  1단계 문제 중 하나입니다. 재미있게 풀어보세요.

 

가로 열쇠

[가로 1번 열쇠]
아직까지 실제로는 없고 영화 등의 소재로 많이 쓰이는 기계. 과거나 미래로 시간 여행을 할 수 있는 기계

[가로 3번 열쇠]
지도에서 사용하는 것으로 해발 고도가 같은 지점을 연결한 곡선. 지도에서 땅이 높고 낮음을 표시하는 방법이다.

[가로 5번 열쇠]
자신의 목적을 달성하기 위해 수단과 방법을 가리지 않는 술책, 모략. 이상한 변호사 우영우에서 권민우 변호사의 별명

 

세로 열쇠

[세로 1번 열쇠]
게임이나 점치기 등에 사용하는 카드. 14세기 경부터 유럽에서 사용되었으며 22장의 우의화 카드와 32장의 점수카드로 구성되어 있다. 이 카드로 점을 보는 ㅇㅇ점집도 꽤 많다.

[세로 2번 열쇠]
조선 태종 때 대궐의 문에 달아, 백성이 억울한 일을 하소연 할 때 치게 만든 북

[세로 4번 열쇠]
친어머니가 아닌, 아버지가 재혼을 하여 생긴 어머니를 이르는 말

 

 

더 많은 십자 낱말퍼즐 게임은 아래 링크에서 "퍼즐킷" 앱을 다운로드 하시면 즐길 수 있어요!

https://play.google.com/store/apps/details?id=com.moa.puzzlekit

 

퍼즐킷 - 낱말퍼즐 퀴즈 - Google Play 앱

가로세로 낱말퍼즐 게임입니다. 게임도 하고 상식도 쑥쑥! 키우세요

play.google.com

 

정답

300x250

본 과정에서는 React와 TypeScript 기반으로 낱말퍼즐 게임을 그려 보겠습니다.  첫 번째 포스트에서는 하려는 작업의 Concept와 환경설정에 대한 부분을 다루어 보겠습니다.

 

Concept

이번에 짬짬이 만들어서 가로세로 낱말퍼즐 게임 앱을 하나 출시했는데, 그 중에 Front-end 요소만 뽑아내어서 진행 과정을 설명해 보겠습니다. 어릴 때 스포츠 신문 등에서 많이 보던 가로세로 낱말퍼즐이라 모양은 익숙하실 것 같습니다.

 

실제로 동작하는 부분은 앱을 다운로드 받으셔서도 확인해 볼 수 있습니다. (안드로이드만 있습니다.)

https://play.google.com/store/apps/details?id=com.moa.puzzlekit 

 

퍼즐킷 - 낱말퍼즐 퀴즈 - Google Play 앱

가로세로 낱말퍼즐 게임입니다. 게임도 하고 상식도 쑥쑥! 키우세요

play.google.com

 

구현한 낱말 퍼즐 모양은 아래 그림과 같이 구성됩니다. (아래는 해당 애플리케이션 캡쳐 화면이며, 본 포스트에서는 퍼즐 모양에 대한 구현만 설명합니다.)

낱말퍼즐 게임

 

위 애플리케이션은 문제은행 방식이라 서버를 가지고 개발했지만 이번 포스트에서는 서버 데이터는 샘플 데이터를 만들어서 사용하겠습니다.

 

환경설정

코드 편집기는 VSCode를 사용하고, React TypeScript를 사용합니다. 패키지 관리도구는 yarn을 사용합니다. 개발에 필요한 환경구성은 아래 포스트를 참고하셔도 되고, 이번 과정에서도 동일한 구성으로 진행하겠습니다.

 

https://redballs.tistory.com/entry/React-%EA%B8%B0%EC%B4%88-%EB%AA%A9%EB%A1%9D-TypeScript-02-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1

 

React 기초 (목록 - TypeScript) 02 - 프로젝트 생성

본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다. 이전 포스트에서 개발할 내용을 확인해 보았으니, 이제 개발에 필요한 환

redballs.tistory.com

 

워크스페이스 생성

먼저 개발에 사용할 워크스페이스 경로를 하나 만들고 패키지를 초기화 합니다. 필요한 파일들의 설치 등은 링크한 포스트를 참고하셔서 설치하시면 됩니다.

D:\workspace> mkdir makePuzzle
D:\workspace> cd makePuzzle
D:\workspace\makePuzzle> yarn init -y

 

연습으로 해보는 프로젝트이므로 패키지 관련 내용은 모두 초기값으로 설정하겠습니다. 위의 과정까지 완료하였으면 VSCode에서 프로젝트를 반입합니다.

 

모듈 설치

VSCode에서 터미널을 하나 열어서 필요한 모듈을 설치합니다. 편의를 위해 위 포스트와 동일한 버전으로 설치하겠습니다.

D:\workspace\makePuzzle> yarn add react@17.0.2
D:\workspace\makePuzzle> yarn add react-dom@17.0.2
D:\workspace\makePuzzle> yarn add --dev babel-core@6.26.3
D:\workspace\makePuzzle> yarn add --dev babel-loader@8.2.3
D:\workspace\makePuzzle> yarn add --dev babel-preset-react-app@10.0.1
D:\workspace\makePuzzle> yarn add --dev webpack@5.66.0
D:\workspace\makePuzzle> yarn add --dev webpack-dev-server@4.7.3
D:\workspace\makePuzzle> yarn add --dev webpack-cli@4.10.0
D:\workspace\makePuzzle> yarn add --dev typescript
D:\workspace\makePuzzle> yarn add --dev @types/react @types/react-dom

 

런타임에 필요한 모듈은 일반 의존성으로, 컴파일 타임에만 필요한 모듈들은 개발 의존성으로 설치하시면 됩니다.

 

Hello World

이제 실제로 프로그램을 작성해서 브라우저에 "Hello World"를 출력하는 부분까지 진행해 보겠습니다.

먼저 프로젝트 루트에 /src 라는 경로를 생성하고 아래와 같이 app.tsx 파일을 작성합니다.

const App = () => (
    <>Hello World</>
);

export default App;

 

동일한 경로에 해당 Component를 사용하는 main.tsx 파일도 아래와 같이 작성합니다.

import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.render(<App />, document.getElementById('app'));

 

위 두 프로그램을 작성했으면 컴파일, 번들링을 진행하여 브라우저에서 한 번 실행해 보겠습니다. 일단은 빨간 줄이 떠도 무시합니다. 먼저 컴파일을 위해 프로젝트 루트에 tsconfig.json 파일을 아래와 같이 작성합니다.

{
    "compilerOptions": {
      "jsx": "react-jsx",
      "target": "es5",
      "module": "commonjs",
      "outDir": "./dist",
      "rootDir": "./src",
      "esModuleInterop": true,
      "forceConsistentCasingInFileNames": true,
      "strict": true,
      "skipLibCheck": true
    }
}

 

컴파일 할 대상은 ./src 하위에 있는 파일들로 설정하며 컴파일 완료한 파일은 ./dist 경로 하위에 배포하도록 설정합니다. 여기까지 완료하였으면 tsc 명령으로 컴파일 할 수 있습니다. 컴파일을 정상적으로 수행하고 나면 프로젝트에 ./dist 라는 경로가 생성되고 하위에 app.js, main.js 파일이 생성됩니다. 현재까지 프로젝트 구조는 아래와 같습니다.

프로젝트 구조

 

컴파일 한 js 파일들을 번들링하기 위해 webpack을 설정합니다. 가장 기본적인 설정만 넣어서 아래와 같이 webpack.config.js 파일을 프로젝트 루트에 작성합니다.

'use strict'
const path = require('path');

module.exports = {
    entry: {
        main: ['./dist/main.js']
    },
    output: {
        path: path.resolve(__dirname, './public/js'),
        filename: 'build.js'
    },
    module: {
        rules: [{
            test: /\.jsx?$/,
            use: {
                loader: 'babel-loader'
            }
        }]
    },
    devServer: {
        static: './public',
        host: 'localhost',
        port: 8080
    }
};

 

실행할 때 시작점은 컴파일 경로인 ./dist/main.js 로 설정하고 개발 서버의 루트는 ./public 하위 경로로 설정합니다. (아직은 만들지 않아서 해당 경로는 아직 없는게 맞습니다.) 포트는 무난하게 8080으로 합니다. 

output 항목에서는 번들링한 결과를 ./public/js/build.js 로 생성하도록 설정합니다.

 

번들링하고 해당 개발 서버를 start 할 수 있도록 package.json 파일에도 script를 추가합니다.

"scripts": {
    "start": "NODE_ENV=development webpack-dev-server",
    "build": "webpack"
}

 

이제 개발 서버의 루트 경로가 될 ./public 경로를 하나 생성하고 index.html을 작성합니다. index.html 파일에서는 번들링 결과인 build.js 파일을 import 합니다.

<!DOCTYPE html>
<html>
    <head>
        <title>낱말퍼즐</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="./js/build.js"></script>
    </body>
</html>

 

Component를 렌더링할 영역으로는 app 라는 이름으로 영역을 하나 만들어 줍니다. 여기까지 완료했으면 이제 위에서 컴파일한 js 파일들을 번들링하여 build.js 로 변환합니다.

D:\workspace\makePuzzle> npm run build

 

명령을 실행하면 번들링 결과로 ./public/js/build.js 파일이 생성됩니다. 현재까지의 모두 진행한 프로젝트 파일 구조는 아래와 같습니다.

프로젝트 구조

 

이제 개발 서버를 시작하고 화면에 Hello World가 출력되는지 확인합니다.

D:\workspace\makePuzzle> yarn start

실행화면

 

이번 포스트에서는 개발 준비 작업으로 프로젝트 구조를 만들고 컴파일, 번들링 및 실행 환경까지 구성해 보았습니다. 다음 포스트에서는 이어서 낱말 퍼즐을 작성해 보겠습니다.

300x250

본 포스트는 MySQL에서 UPDATE나 DELETE를 하려고 할 때 발생하는 에러(오류코드 1093)에 대한 설명입니다. 상세 오류 메시지는 You can't specify target table 'TABLE' for update in FROM clause 와 같이 표시됩니다.

 

오라클이나 티베로 등에서는  아래와 같은 Query가 잘 동작합니다.

UPDATE  TBP_STAGE_HIST
   SET  COMPLETE = 'Y'
 WHERE  ST_DTM = 
     (  SELECT  MAX(ST_DTM)
          FROM  TBP_STAGE_HIST)

위 예시에는 UPDATE를 사용했지만 DELETE 역시 잘 동작합니다. 하지만 MySQL에서는 1093 에러 코드를 리턴합니다. 내용을 보면 You can't specify target table 'TABLE' for update in FROM clause 로, UPDATE나 DELETE 작업을 수행할 때 자기 자신을 직접 참조할 수 없도록 합니다.

 

위의 경우 해결은 간단합니다. Sub Query에 자기 자신을 사용할 경우 인라인 뷰 형태로 만들어서 사용하시면 됩니다. 위의 Query를 수정하면 아래와 같은 방식입니다.

UPDATE  TBP_STAGE_HIST
   SET  COMPLETE = 'Y'
 WHERE  ST_DTM =
     (  SELECT  ST_DTM
          FROM (SELECT  MAX(ST_DTM) ST_DTM
                  FROM  TBP_STAGE_HIST) A
     )

 

300x250

'DB' 카테고리의 다른 글

[Oracle, Tibero] 테이블 레이아웃 조회  (0) 2022.07.11

본 포스트는 애플 앱 스토어(Apple AppStore)에서 앱을 서비스하기 위해 검수 요청할 때 애플에서 요구하는 Native Apple Map을 지원하기 위한 내용을 다룹니다.

 

앱 서비스를 하나 만들어서 먼저 구글 플레이스토어에 런칭한 후 아이폰 앱도 만들어서 애플 앱 스토어에 검수를 요청했는데, 대강 아래와 같은 사유로 Reject 메시지가 전달 되었습니다.

 

Guideline 4.0 - Design
Your app's location feature is not integrated with the built-in mapping functionality, which limits users to a third-party maps app

 

검수를 요청한 앱에는 특정 판매점을 찾아가기 위한 길찾기 기능이 포함되어 있었고, 길찾기 기능을 선택하면 휴대폰에 설치되어 있는 네이버 맵을 호출하여 길찾기 기능을 제공하는 형태였습니다. (아래 그림 참고)

길찾기 기능

 

구글 플레이스토어 심사 당시에는 문제가 되지 않았지만 애플에서는 디자인 가이드 라인에 따라서 위 내용과 같이 third-party 앱이 아닌 이미 built-in 되어 있는 애플 맵을 통해 길찾기 기능을 제공해야 한다는 Review 였습니다. 사실 애플 맵에 길찾기 기능이 있는 것도 확인해보지 않았었고, 대부분 길찾기 기능을 네이버 맵을 사용하기에 수정은 하지 않고 회신 처리했습니다.

 

회신 내용
한국에서는 대부분 네이버 맵을 사용하여 길찾기 기능을 제공하고, 사용자의 휴대폰에 설치되어 있지 않은 경우 손쉽게 설치할 수 있도록 앱 스토어로 안내합니다. 모든 지도 기능을 애플 맵을 통해 제공하라는 무리가 있습니다.

 

어느 정도 납득이 될 줄 알았지만 애플에서는 다시 Reject이 왔습니다. 사유는 대강 아래와 같습니다.

 

Guideline 4.0 - Design
Specifically, your app  limits users to a third-party maps app, but does not give users the option to launch the native Apple Maps app. Apps that use a third-party maps, need to offer native Apple Maps to users as an equivalent option.

 

다른 앱으로 서비스를 반드시 해야 한다면 사용자가 애플 맵도 선택할 수 있게 동등한 옵션을 주라는 권고강제였습니다. 하여 Apple Map API를 찾아보니 의외로 많은 기능들이 있었습니다. 아래 링크에 상세한 API 가이드가 나와 있습니다.

https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html

 

Map Links

Map Links The maps URL scheme is used to show geographical locations and to generate driving directions between two points. If your app includes address or location information, you can use map links to open that information in the Maps app in iOS or macOS

developer.apple.com

 

개발하실 일이 있으면 아래 소스코드를 참고하시면 됩니다.

 

1. Android 네이버 맵 연계

const mapUrl: string = 'intent://route/walk?appname=testApp&slat=' + slat + '&slng=' + slng + '&sname=현재위치&'
                     + 'dlat=' + store.lat + '&dlng=' + store.lon + '&dname=' + encodeURIComponent(store.storeName)
                     + '#Intent;scheme=nmap;action=android.intent.action.VIEW;category=android.intent.category.BROWSABLE;package=com.nhn.android.nmap;end';
location.href = mapUrl;

 

2. Web or iOS 앱에서 네이버 맵 연계

const mapUrl: string = 'nmap://route/walk?appname=testApp&slat=' + slat + '&slng=' + slng + '&sname=현재위치&'
                     + 'dlat=' + store.lat + '&dlng=' + store.lon + '&dname=' + encodeURIComponent(store.storeName);
location.href = mapUrl;

 

3. Apple Map 연계

const mapUrl: string = 'maps://?t=m&saddr=' + slat + ',' + slng + '&daddr=' + store.lat + ',' + store.lon;
location.href = mapUrl;

 

300x250

본 포스트는 휴대폰의 위치 기능을 사용해서 현재 나와 가까운 곳, 근처에 있는 로또 1등 판매점을 구하는 방법에 대한 설명이며, 개발환경은 Node.js, NestJs, MySQL, React 입니다.

 

후배와 사이드 프로젝트로 로또 앱을 하나 만들면서 언젠가부터 로또에 진심이 되어 버렸습니다. 43년을 살면서 복권 한 장 사보지 않았었지만 이제 매주 빼먹지 않고 로또를 구매하고 있습니다. 이 블로그에서 설명하는 결과물은 아래 앱에서 보실 수 있습니다. 

 

구글 플레이스토어, 애플  앱 스토어에서 "로또킷"으로 검색하시면 되고, 링크는 아래와 같습니다.

 

[구글 플레이스토어]

https://play.google.com/store/apps/details?id=com.nextact.lottoapp 

 

로또킷 - 로또 선물함 - Google Play 앱

로또 행운번호 생성, 주변 로또판매점 바로가기, 로또 번호 선물하기 등 다양한 로또 정보를 제공하는 로또킷(lottoKit)입니다.

play.google.com

 

[애플 앱 스토어]

http://itunes.apple.com/app/id1658994153

 

‎로또킷

‎로또킷(lottoKit) 제공 기능을 소개 합니다 - 로또킷 A.I 분석 및 랜덤 조건으로 로또 행운번호를 자동생성 해보세요 - 로또킷은 내 주변 1등 로또 판매점을 안내해주고 바로 찾아갈 수 있도록 길

apps.apple.com

 

다시 본론으로 넘어가서 위 앱에서 내 위치와 가장 가까운 곳에 있는 로또 1등 판매점을 구하는 로직을 설명하겠습니다.

 

1. 1등 판매점 DB 생성

사용자의 위치 기반으로 연산을 해야 하기 때문에 1등 판매점 정보를 DB로 가지고 있어야 합니다. 개발할 당시에는 API도 없거니와 API가 있다고 해도 매번 트래픽을 발생시켜서 구하기 위해서는 사용자가 상당한 시간을 기다려야 합니다.

 

일단 간단히 아래와 같이 테이블을 만들어서 데이터를 적재합니다. (이 부분은 여러 방법이 있으므로 사용할 테이블 구조에 대해서만 설명하겠습니다.)

CREATE TABLE TB_TOP_STORE (
    TIMES INT NOT NULL,
    CODE VARCHAR(20) NOT NULL,
    NAME VARCHAR(40),
    ADDR VARCHAR(120),
    LAT VARCHAR(14),
    LON VARCHAR(14)
)

 

컬럼 순서대로 당첨회차, 판매점 코드, 판매점 명, 판매점 주소, 위도, 경도 입니다. 

 

2. 1등 판매점 기준 정하기

글을 작성하는 현재 기준에도 이미 1048회를 판매하고 있습니다. 1회에 당첨되거나 한 곳은 크게 의미가 없을 것 같아서 저 같은 경우는 최근 10회차 이내에 1등을 배출한 지점을 1등 판매점이라고 정의했습니다.

 

위 테이블 구조에서 최근 10회차 이내의 1등 당첨 판매점을 구하는 Query는 아래와 같이 작성합니다. (한 회차, 한 지점에서 여러 건의 1등 당첨이 있을 수 있으므로 DISTINCT 로 SELECT 합니다.)

SELECT  DISTINCT TIMES
     ,  CODE
     ,  NAME
     ,  ADDR
     ,  LAT
     ,  LON
  FROM  TB_TOP_STORE
 WHERE  TIMES > (SELECT  MAX(TIMES)
                   FROM  TB_TOP_STORE) - 10

 

3. 서버 API 만들기

위 Query를 결과에서 사용자의 위치와 가장 가까운 판매점을 구하는 서버 로직은 대강 아래와 같습니다. 사용자의 위치는 클라이언트 애플리케이션에서 현재 위치의 위도와 경도를 전달 받아야 합니다.

 

위 테이블의 데이터 구조를 받는 데이터 타입은 아래와 같이 정의합니다.

interface IStoreInfo {
    times: number;
    code: string;
    name: string;
    addr: string;
    lat: string;
    lon: string;
    dist: number;
}

 

테이블 구조와 동일하게 선언하되 현재 위치와의 거리를 연산하기 위해 dist라는 항목만 추가합니다.

 

Repository에서 데이터를 SELECT 합니다.

async selectTopStore(): Promise<IStoreInfo[]> {
    const topStore: IStoreInfo[] = await getConnection().query(this.queries.selectTopStore) as IStoreInfo[];
    return topStore;
}

 

여기까지 진행하면 10회차 이내의 1등 판매점이 모두 추출됩니다. 거리 오름차순으로 3건만 추출해야 하므로 먼저 아래 로직을 사용해서 사용자와 판매점 간의 거리를 구합니다.

/**
 * 두 지점 사이의 거리를 구한다.
 * @param slat 출발지 위도
 * @param slon 출발지 경도
 * @param dlat 도착지 위도
 * @param dlon 도착지 경도
 */
getDist(slat: number, slon: number, dlat: number, dlon: number): number {
    if (slat === dlat && slon === dlon) return 0;

    const radSlat: number = Math.PI * slat / 180;
    const radDlat: number = Math.PI * dlat / 180;
        
    const theta: number = slon - dlon;
    const radTheta: number = Math.PI * theta / 180;

    let dist: number = Math.sin(radSlat) * Math.sin(radDlat)
                     + Math.cos(radSlat) * Math.cos(radDlat) * Math.cos(radTheta);
    if (dist > 1) dist = 1;
        
    dist = Math.acos(dist);
    dist = dist * 180 / Math.PI;
    dist = dist * 60 * 1.1515 * 1.609344 * 1000;

    dist = dist < 100 ? Math.round(dist / 10) * 10 : Math.round(dist / 100) * 100;
    return dist;
}

 

위 코드를 이용해 지점 간의 거리를 구한 후 오름차순 정렬하여 가장 가까운 3건을 추출합니다.

async selectTopStore(lat: string, lon: string): Promise<IStoreInfo[]> {
    const topStore: IStoreInfo[] = await this.storeRepository.selectTopStore();

    const sorted: IStoreInfo[] = topStore.map((store: IStoreInfo) => {
        const dist: number = this.getDist(Number(lat), Number(lon), Number(store.lat), Number(store.lon));
        store.dist = dist;
        return store;
    }).sort((a: IStoreInfo, b: IStoreInfo) => a.dist - b.dist);

    return sorted.filter((store: IStoreInfo, inx: number) => inx < 3); 
}

 

4. 클라이언트에서 현재 위치 구하기

클라이언트에서는 아래 코드를 이용하여 현재 좌표를 구하여 서버로 전달하면 됩니다.

navigator.geolocation.getCurrentPosition((pos: GeolocationPosition) => {
    const slat: number = pos.coords.latitude;
    const slng: number = pos.coords.longitude;
});

 

위 코드의 결과물은 아래와 같습니다.

300x250

본 포스트는 JavaScript 환경에서 Session Key나 사용자 별, 앱 별로 키 값이 필요할 경우 난수로 키를 조합하는 방법에 대한 내용입니다.

 

아래 코드는 Node.js 환경에서 앱이 서버에 접속할 때 신규로 키를 채번하는 부분의 소스코드이며 TypeScript 로 작성했습니다.

const generateAppKey = (keyLength: number): string => {
    const baseChars: string = 'ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789';
    let keyString: string = '';
        
    for (let inx=0; inx<keyLength; inx++) {
        keyString += baseChars.charAt(Math.floor(Math.random() * baseChars.length))
    }

    return keyString;
};

 

생성하려는 키는 영문 대/소문자 및 숫자만 허용하므로 사용할 기본 문자열을 하나 선언한 후 원하는 키 길이만큼 반복하며 Random으로 채번하면 됩니다.

 

채번한 후에는 해당 값이 유니크해야 하므로

1. DB에 키를 저장하는 경우에는 DB에 동일한 값이 없는지 확인

2. WAS 메모리에 저장할 경우에는 WAS 메모리에 동일한 값이 없는지 확인

 

하시면 됩니다.

300x250

본 포스트는 React 애플리케이션 작성 시 로딩 다이얼로그, 프로그레스를 표시할 때 사용할 수 있는 오픈 소스인 React Spinners 사용에 대한 글입니다.

 

 

애플리케이션에서 서버 인터페이스나 모바일 Device의 기능을 사용하기 위해서 인터페이스 할 때, 아니면 내부적으로 무언가를 분석해야 해서 사용자와의 인터페이스를 잠시 막아두고 싶은 경우 Loading Dialog를 많이 사용합니다. jQuery를 사용할 때도 괜찮은 오픈 소스가 있어서 사용하거나, 아니면 그냥 Layer를 하나 뒤집어 씌운 다음에 괜찮은(?) 그림이나 애니메이션 하나를 올리는 방법을 사용했습니다.

 

React 애플리케이션에서 사용하기 괜찮은 오픈 소스가 하나 있어 소개합니다. 사실 코드에 대한 설명은 더 필요없을 정도로 깔끔하게 아래 사이트에 정리되어 있습니다.

https://mhnpd.github.io/react-loader-spinner/docs/intro

 

Getting Started | React Spinners

Installation

mhnpd.github.io

 

저 같은 경우는 Audio Component가 마음에 들어서, 해당 Component를 아래와 같이 사용해 보았습니다. 먼저 Loading Progress로 사용할 Component를 하나 생성합니다.

const Loading = () => {
    return (
        <div className="loadingContainer">
            <Audio
                height="60"
                width="60"
                color="#FFF"
                ariaLabel="audio-loading"
                wrapperStyle={{}}
                wrapperClass="wrapper-class"
                visible={true}
            />
        </div>
    )
};

 

Audio Component를 사용하고 백그라운드를 애플리케이션에 어울리는 형태로 커스터마이징 하기 위해서 loadingContainer라는 style을 사용하는 div로 한 번 감쌌습니다. 해당 CSS는 아래와 같습니다.

.loadingContainer {
    width:100%;
    height:100%;
    overflow:hidden;
    position:fixed;
    top:0;
    right:0;
    bottom:0;
    left:0;
    background-color:rgba(0,0,0,0.4);
    z-index:100;
    text-align:center;
}
.loadingContainer svg {
    position:absolute;
    top:calc(50% - 30px);
    left:calc(50% - 30px);
}

 

백그라운드를 화면 전체에 반투명으로 적용하고 Component의 위치를 선언했습니다. 이제 Loading Component를 사용할 Component 내부에 state를 선언합니다.

const [loading, setLoading] = useState<boolean>(false);

 

처음에는 Loading Component는 표시되지 않아야 하므로 초기 state는 false로 선언합니다. 이제 UI를 블록할 필요가 있는 부분에 Loading Component를 적용합니다. 아래 예시는 모바일 데이터를 통해 현재 위치를 가져오는 부분에 적용한 예시입니다.

setLoading(true);
navigator.geolocation.getCurrentPosition((pos) => {
    const slat: number = pos.coords.latitude;
    const slng: number = pos.coords.longitude;
	//TODO :: 구현
    setLoading(false);
}, () => {
    setLoading(false);
});

 

현재 위치를 수신하기 전에 loading state를 true로 변경한 후 작업이 정상으로 완료되거나, 실패로 완료되어도 loading state는 false로 변경합니다. 이제 마지막으로 UI Rendering code를 return 하는 부분에 state에 변경에 따라 Loading Component를 표시하거나 제거하는 로직을 추가합니다.

{
    loading ? <Loading /> : ''
}

 

위와 같이 작업하면 간단하게 Loading Dialog를 표시할 수 있습니다. 위의 예시를 사용한 화면은 아래와 같습니다.

 

300x250

블로그에 정리한 내용들 중 React와 Express로 애플리케이션을 구성하여 이전 회차들의 당첨번호를 수집하여, 해당 데이터들로 통계를 분석하여 로또 추천(예측/예상) 번호를 생성해주는 애플리케이션을 사이드 프로젝트로 만들어 보았습니다.

 

아래 URL에서 다운로드 하실 수 있습니다.

https://play.google.com/store/apps/details?id=com.nextact.lottoapp

 

로또킷(lottoKit) - Google Play 앱

로또 추천번호 생성, 주변 로또판매점 바로가기, 로또 번호 선물하기 등 다양한 로또 정보를 제공하는 로또킷(lottoKit)입니다.

play.google.com

 

 

 

애플리케이션은 아래 포스트에서 설명한 구조로 구성되어 있습니다.

https://redballs.tistory.com/entry/React-Express-Typescript-애플리케이션-구성

 

React + Express + Typescript - 01. 서버 환경 구성

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API

redballs.tistory.com

 

Back-end는 살짝 변경해서 NestJS 기반으로 구성했지만 별반 다를 내용은 없습니다. Front-end는 React + Typescript로 구성해 보았습니다. 원리는 수집한 자료를 기반으로 나름의 알고리즘으로 서버에서 분석을 해서 번호를 생성한 후 Front-end에 전달하면 Front-end에서 추천번호를 보여주는 방식입니다.

 

UX는 아래와 같이 구성되어 있습니다.

Lotto-Kit 메인 화면

 

로또 번호를 5개까지 생성할 수 있고 (마음에 들지 않는 번호는 재생성할 수 있습니다.) 근처에 있는 1등 당첨 판매점과 일반 로또 판매점을 조회할 수 있습니다. 또한 우리 앱에서 생성한 번호 중 실제로 1등~5등에 당첨된 번호가 있으면 [당첨내역]에서 조회할 수 있습니다. 

그리고 회차 별 당첨번호도 조회할 수 있고, 생성한 번호를 지인에게 선물할 수도 있습니다.

 

한 번쯤 애플리케이션에 관심이 있으신 분은 다운로드 하셔서 평가 부탁 드립니다.

 

구글 플레이스토어에서 "lottokit"을 검색하시거나 아래 URL로 바로 다운로드 하실 수 있습니다.

https://play.google.com/store/apps/details?id=com.nextact.lottoapp

 

로또킷(lottoKit) - Google Play 앱

로또 추천번호 생성, 주변 로또판매점 바로가기, 로또 번호 선물하기 등 다양한 로또 정보를 제공하는 로또킷(lottoKit)입니다.

play.google.com

 

300x250

이 포스트는 Java 에서 여러 개의 단어 조건을 equals로 비교할 때 equals 대신 Arrays.asList().contains 를 사용하는 방법을 설명합니다.

 

 

Java 업무 프로그램을 작성하다 보면 A이거나 B이거나 C일 경우에 어떤 코드를 실행시키는 형태의 조건문을 많이 작성합니다. 이렇게 조건이 몇 개 없을 때는 equals를 사용해도 가독성이 나쁘지 않고, 변경에도 문제가 없지만 조건이 많을 경우에는 프로그램도 길어져 가독성이 떨어질 뿐만 아니라 변경 시에도 좀 헷갈리는 경우가 있습니다.

String cond = request.getParameter("cond");

if ("A".equals(cond) || "B".equals(cond) || "C".equals(cond)) {
    //Do something
}

 

JavaScript에서 조건을 배열로 만든 후 indexOf나 includes로 조건문을 사용한 것과 동일하게 Java에서도 Arrays.asList를 아래와 같이 사용할 수 있습니다.

String cond = request.getParameter("cond");

List condList = Arrays.asList(new String[] {"A", "B", "C"});
if (condList.contains(cond)) {
    //Do something
}

 

위와 아래의 조건문은 동일한 코드이지만 가독성이나 변경을 생각하면 아래 쪽이 조금 더 용이해 보입니다.

JavaScript에서 indexOf, includes를 사용하는 방법은 아래 포스트를 참고합니다.

조건문에서 indexOf, includes 활용 :: 즐거운인생 (tistory.com)

 

조건문에서 indexOf, includes 활용

JavaScript에서 여러 개의 조건을 나열할 때 아래와 같이 or를 길게 연결해서 사용합니다. const code = data.code; if (code === 'A' || code === 'B' || code === 'C' || code === 'D' || code === 'E') { //Do..

redballs.tistory.com

 

300x250

본 포스트는 Express 기반의 Node.js 애플리케이션 서버를 구성할 때 mysql2 라이브러리를 이용하여 MySQL 연계 환경을 구성하는 방법을 설명합니다.

 

Node.js Express mysql2 환경 설정

 

express 서버의 기본적인 환경 설정은 이전 포스트인 아래 링크를 참고하여 설정합니다. 아래 포스트에서 설정한 내용에 이어서 MySQL 환경 구성을 진행해보겠습니다.

 

Node.js Express 환경 설정 :: 즐거운인생 (tistory.com)

 

Node.js Express 환경 설정

본 포스트는 Express 기반의 Node.js 애플리케이션 서버를 구성하고 static 경로를 설정하는 방법을 설명합니다. 서론 Node.js 서버 애플리케이션을 개발할 때 주로 사용하는 Framework는 Express와 Nest 정도

redballs.tistory.com

 

MySQL 설치

먼저 사용할 데이터베이스를 로컬에 설치 해보겠습니다. MySQL은 아래 링크에서 자신의 환경에 맞는 설치 파일을 다운로드하여 설치합니다.

https://dev.mysql.com/downloads/mysql/

 

MySQL :: Download MySQL Community Server

Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris macOS Source Code Select OS Version: All Windows (x86

dev.mysql.com

 

설치 과정은 별로 특별할 것이 없으므로 별도로 설명하지 않습니다. 설치 과정 중 MySQL root 계정을 설정하는 부분이 있는데 본인이 사용할 root 계정을 정확하게 입력하고 기억합니다.

 

접속정보 설정

설치가 완료되었다면 MySQL Workbench를 켜고 일단 root 계정으로 접속합니다.

MySQL Workbench

 

위 스크린 샷의 MySQL Connections 부분에 [+] 버튼을 클릭하여 새로운 Connection을 하나 생성하는데, 계정의 비밀번호를 Keychain에 저장하고 싶으면 [Store in Keychain ...] 버튼을 클릭하여 비밀번호를 기억시켜 둡니다. 여기서는 굳이 비밀번호는 저장하지 않겠습니다.

새 Connection 생성

식별할 수 있는 Connection Name과 접속 정보를 확인한 후 [OK] 버튼을 선택하면 아래와 같이 Connection이 하나 생성됩니다.

Connection 생성 확인

 

선택하고 비밀번호를 입력하여 root 계정으로 로그인 합니다.

데이터베이스 접속

 

데이터베이스 생성

이제 사용할 데이터베이스 및 사용자를 하나씩 생성해 보겠습니다. 먼저 아래 스크립트로 데이터베이스를 먼저 생성합니다.

CREATE DATABASE testDB DEFAULT CHARACTER SET utf8 collate utf8_general_ci;

 

testDB 라는 이름의 데이터베이스를 하나 생성하고 해당 데이터베이스의 기본 Charater-set을 UTF-8로 설정합니다. 이어서 사용자 계정도 하나 생성합니다. testid 라는 계정을 생성하고 비밀번호는 test01!로 생성하겠습니다.

CREATE USER 'testid'@'%' IDENTIFIED BY 'test01!';

 

이제 생성한 계정에 testDB의 개체들을 사용할 수 있는 권한을 부여합니다. 

GRANT ALL PRIVILEGES ON testDB.* to 'testid'@'%';

 

다시 로그인해서 정상적으로 로그인이 되는지 확인합니다. 위에서 하신대로 [New Connection]을 하나 열고 로그인하시면 됩니다.

생성한 계정으로 로그인

테스트에 사용할 테이블을 하나 생성합니다.

CREATE TABLE TB_TEST (
    TEST_ID int,
    TEST_TXT varchar(100),
    PRIMARY KEY(TEST_ID)
)

 

생성한 테이블에 데이터를 한 건 입력하고 SELECT 해서 테이블이 정상적으로 생성되었는지 확인합니다.

INSERT INTO TB_TEST VALUES (1, 'TEST');

SELECT  *
  FROM  TB_TEST

 

Node.js 프로젝트 설정

위와 같이 데이터베이스에 대한 설정을 마쳤으면 이제 Node.js 에서 해당 데이터베이스를 사용하도록 설정합니다. 위의 포스트를 따라해서 "Hello World"까지 출력해 본 상태라면 현재 package.json은 express 모듈만 설치하여 아래와 같은 상태입니다.

{
  "name": "express",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.1"
  },
  "scripts": {
    "start": "node ./src/app.js"
  }
}

 

MySQL 데이터베이스를 연동하기 위해 mysql2 모듈을 설치합니다.

D:\workspace\expressServer>yarn add mysql2

 

MySQL 연동을 위한 소스코드를 app.js 파일에 추가합니다. mysql2 모듈을 추가하고 Connection Pool을 생성합니다. 위에서 생성한 데이터베이스 및 계정 정보를 입력하여 생성합니다.

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
    host: 'localhost',
    port: '3306',
    user: 'testid',
    password: 'test01!',
    database: 'testDB'
});

const getConn = async() => {
    return await pool.getConnection(async (conn) => conn);
};

 

testSelect라는 Route를 추가하고 간단하게 TB_TEST 테이블의 정보를 조회하는 쿼리를 실행하도록 작성합니다.

app.get('/testSelect', async (req, res) => {
    const conn = await getConn();
    const query = 'SELECT TEST_ID, TEST_TXT FROM TB_TEST';
    let [rows, fields] = await conn.query(query, []);
    conn.release();

    res.send(rows);
});

 

위 내용들을 추가한 app.js의 전체 소스코드는 아래와 같습니다.

const express = require('express');
const path = require('path');
const app = express();
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
    host: 'localhost',
    port: '3306',
    user: 'testid',
    password: 'test01!',
    database: 'testDB'
});

const getConn = async() => {
    return await pool.getConnection(async (conn) => conn);
};

app.use(express.static(path.join(__dirname, '../public')));
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, '../public/index.html'));
});

app.get('/testSelect', async (req, res) => {
    const conn = await getConn();
    const query = 'SELECT TEST_ID, TEST_TXT FROM TB_TEST';
    let [rows, fields] = await conn.query(query, []);
    conn.release();

    res.send(rows);
});

app.listen('8001', () => {
    console.log('Server started');
});

 

실행 결과 확인

브라우저에 http://localhost:8001/testSelect 를 입력하여 접근하면 아래와 같은 결과를 확인할 수 있습니다.

[{"TEST_ID":1,"TEST_TXT":"TEST"}]

 

300x250

'Node.js > express 환경구성' 카테고리의 다른 글

Node.js Express 환경 설정  (0) 2022.07.15

+ Recent posts