728x90

샘플 코드만 적어둔다.

Android 네이버 Map 라이브러리를 이용하여 검색(Search)으로 위치 이동과 더불어 지도 레벨을 지정해야 할 때 필요한 코드이다.

 

private fun LocationAt(latitude: Double, longitude: Double) {
    mNaverMap?.locationTrackingMode = LocationTrackingMode.None
    val coord = LatLng(latitude, longitude)
    val cameraPosition = CameraPosition(coord, 16.0)
    mNaverMap.cameraPosition = cameraPosition
    val cameraUpdate = CameraUpdate.scrollTo(coord).animate(CameraAnimation.Easing, 300)
    mNaverMap!!.moveCamera(cameraUpdate)
}

 

 

블로그 이미지

Link2Me

,
728x90

실리콘음극재의 치명적인 단점을 커버할 수 있는 것이 그래핀이다.
기존의 배터리는 충방전을 계속하면 입자에 균열이 생기고 용량도 감소된다.
여러번 충방전을 계속해도 균열이 거의 일어나지 않는 배터리 성능을 유지할 수 있는 비결은 그래핀이다.
음극재 핵심원료 흑연 공급 부족으로 대체재 그래핀이 부각
현재 대주전자재료에서 실리콘음극재를 5% 수준에서 8%수준으로 향상시키겠다고 한 바있다.
흑연에서 100% 실리콘음극재로 대체되는 시기는 2027년으로 전망하고 있다.

크리스탈신소재
- 세계 최대 규모의 그래핀 생산기업으로 자리매김하기 위해 생산라인 증설 및 양산화 진행 중
- 음극재 핵심소재 흑연, 그래핀 공급부족 이슈때마다 급등 전망
- 국내 유일 그래핀 매출로만 3년 연속 흑자 달성
- 내일의 급등주 유투버 추천
  1차 목표가 3,980원(+12%), 2차 목표가 4,600원(+30%)
  외국인 5.12일(금) 448,722주 순매수, 기타법인 1,480,533주 순매수

 

 

회사 측에 따르면 이번에 개발한 신형 그래핀 용액은 샘플이 최대 입경 10마이크로미터(μm) 미만, 평균 입경 2.495㎛에 불과하다. 특히 신형 분산제를 사용해 수중 분산이 가능한 특성을 가졌다고 회사 측은 설명했다.

실제 크리스탈신소재가 최신 기술로 생산한 그래핀은 ▲얇은 두께 ▲균일한 직경 분포 ▲적은 구조적 결함 ▲높은 전도율 ▲다양한 크기 ▲균일한 크기와 두께 ▲큰 직경 비율 ▲높은 구조적 완정성 ▲우수한 기계적 성질 등 다양한 특징을 가진다.

해당 신형 그래핀 분산액을 황산구리 등이 주 원료인 원료액에 첨가해 전기분해 동박을 제조할 경우 그래핀 비율은 황산구리 용액의 1만 분의 3에 불과하다. 크기가 작기 때문에 그래핀 분산액은 해당 용액에 담가둬도 침전현상이 일어나지 않는 것이다.

전기분해 동박은 동 클래드 적층판(CCL), 인쇄 회로 기판 (PCB), 신재생 리튬이온배터리 등 분야에서 핵심 원자재로 쓰인다.

현재 내구성이 강한 코팅, 동력 배터리, 열전도 자재, 복합섬유·금속 복합 자재 등 다양한 분야에서 광범위하게 활용되고 있다고 회사 측은 설명했다.

블로그 이미지

Link2Me

,
728x90

네이버 증권에서 일별 시세 정보를 가져오는 코드이다.

아래 코드로는 100% 만족된 결과를 도출하지 못한다.

IF 조건문을 사용해서 원하는 결과를 도출하는 건 개발자의 몫이다.

또한 DB에 저장되는 순서를 날짜가 적은 데이터를 먼저 저장하기 위한 방법으로 수정하는 것이 좋다.

 
import requests
from fake_useragent import UserAgent
# BeautifulSoup은 HTML 과 XML 파일로부터 데이터를 수집하는 라이브러리
from bs4 import BeautifulSoup
# BeautifulSoup 에서는 Xpath 사용 불가능
import pymysql
import re
 
def financialSiseDayData(code,page):
    
    ua = UserAgent()
    headers = {'User-agent': ua.ie}
    url = f'https://finance.naver.com/item/sise_day.naver?code={code}&page={page}'
    # print(url)
 
    res = requests.get(url, headers=headers)
    #
    if res.status_code == 200:
        html = res.text
 
        # HTML 페이지 파싱 BeautifulSoup(HTML데이터, 파싱방법)
        soup = BeautifulSoup(html, 'html.parser')
 
        # find() : 가장 먼저 검색되는 태그 반환
        body = soup.select_one("table")
        # print(body)
 
        items = body.find_all('tr', {'onmouseover':'mouseOver(this)'})
        # print(items)
 
        conn = pymysql.connect(host="localhost", user="", password="", db="studydb", charset="utf8")
        curs = conn.cursor()
 
        for item in items:
            information = item.text
            info = information.split('\n')
            # print(info)
            # print(info[2], "type: ", type(info[2]))
            # print(code, info[1],info[2],info[8],info[9],info[10],info[11],info[5].strip())
            closeM = re.sub(r'[^0-9]''', info[2])
            openM = re.sub(r'[^0-9]''', info[8])
            highM = re.sub(r'[^0-9]''', info[9])
            lowM = re.sub(r'[^0-9]''', info[10])
            volume = re.sub(r'[^0-9]''', info[11])
 
            # 기존 DB에 있는 데이터인 경우에는 중복 저장하지 말고 무시하라.
            sql = "insert ignore into stock_day (code,date,close,open, high, low, volume) values (%s, %s, %s, %s, %s, %s, %s)"
            val = (str(code), info[1], closeM, openM, highM, lowM, volume)
            curs.execute(sql,val)
            conn.commit()
        conn.close()
 
    else:
        print(res.status_code)
 
 
if __name__ == '__main__':
    code = 140670
    for i in range(14): # 1개월 데이터만 추출
        financialSiseDayData(code,str(i))
 
    print('완료되었습니다.')

 

DB에 저장하기 위한 SQL 코드

CREATE TABLE stock_day (
  idx int(11NOT NULL,
  code varchar(8NOT NULL COMMENT '종목코드',
  date char(10NOT NULL COMMENT '날짜',
  close double NOT NULL DEFAULT 0 COMMENT '종가',
  open double NOT NULL DEFAULT 0 COMMENT '시가',
  high double NOT NULL DEFAULT 0 COMMENT '고가',
 low double NOT NULL DEFAULT 0 COMMENT '저가',
  volume double NOT NULL DEFAULT 0 COMMENT '거래량',
  display int(2NOT NULL DEFAULT 1,
  reg_date timestamp NOT NULL DEFAULT current_timestamp()
ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
ALTER TABLE stock_day
  ADD PRIMARY KEY (idx),
  ADD UNIQUE KEY code_date (code,date);
 
ALTER TABLE stock_day
  MODIFY idx int(11NOT NULL AUTO_INCREMENT;
COMMIT;

코드와 날짜를 UNIQUE KEY 로 설정하였다.

 

 

 

블로그 이미지

Link2Me

,
728x90

초보수준으로 약간 배우고 오랫만에 Python 을 사용하니까 생각나는게 하나도 없어서 테스트하면서 적어둔다.

 

가장 먼저 해야 할일은 크롬 브라우저에서 개발자 모드(F12) 상태에서 원하는 정보를 클릭하는 것이다.

 

 

//*[@id="tab_con1"]/div[1]/table/tbody/tr[3]/td/em

XPath 복사 결과는 위와 같다.

 

selector 복사를 하면....

#tab_con1 > div.first > table > tbody > tr:nth-child(3) > td > em

와 같은 결과를 반환해준다.

이 결과를 그대로 사용하면 원하는 결과가 나오지 않기 때문에 태그 구조를 파악하면서 결과를 얻어내야 한다.

 

원하는 정보를 가져오기 위한 준비를 마치고 Code 상에서 확인하는 작업을 한다.

텍스트 형태의 데이터에서 원하는 HTML 태크를 추출하는 쉽게 도와주는 라이브러리가 BeautifulSoup 이다.

 

먼저 table 태그를 출력해보고 원하는 자료가 몇번째 tr 에 있는지 확인하고 원하는 값을 추출한다.

데이터의 형태가 추출하기 쉽게 되어 있지 않는 경우도 있으니 split('\n') 를 사용하여 배열 구조를 파악해서

원하는 결과를 출력해야 하는 시가총액 정보도 있다.

import requests
# BeautifulSoup은 HTML 과 XML 파일로부터 데이터를 수집하는 라이브러리
from bs4 import BeautifulSoup
# BeautifulSoup 에서는 Xpath 사용 불가능
 
def financialData(code):
    url = 'https://finance.naver.com/item/sise.naver?code='+ str(code)
    res = requests.get(url)
 
    if res.status_code == 200:
        html = res.text
 
        # HTML 페이지 파싱 BeautifulSoup(HTML데이터, 파싱방법)
        soup = BeautifulSoup(html, 'html.parser')
 
        # parents : 선택한 요소 위로 올라가면서 탐색하는 도구
        # next_siblings : 동일 레벨에 있는 태그들을 가져온다.
 
        # //*[@id="tab_con1"]/div[1]/table/tbody/tr[3]
 
        # find() : 가장 먼저 검색되는 태그 반환
        body = soup.select_one("#tab_con1 > div.first > table")
        print(body.prettify()) # 보기 좋게 출력
 
        # 시가총액 가져오기
        market_sum_init= soup.select('#tab_con1 > div.first > table > tr')[0].find('td').text.split('\n')
        print(market_sum_init)
        market_sum = market_sum_init[4].strip() + market_sum_init[7].strip()
        print(market_sum)
 
        # 상장 주식수 가져오기
        stock_num = soup.select('#tab_con1 > div.first > table > tr')[2].find('td').text
        print(stock_num)
 
    else:
        print(res.status_code)
 
if __name__ == '__main__':
   financialData(140670)

 

거래량 가져오기

find는 가장 가까운 태그 하나를 찾는다.

find(태그,Class명)

find(태그, id='address')

 

개발자모드에서 select 값을 찾아 보니 아래와 같다.

이걸 활용하여 soup.select('#chart_area > div.rate_info > table > tr')[0] 를 한 다음에 print 출력으로 구조를 파악한다.

td 태그 중에서 3번째 태크가 원하는 값이 있다는 걸 확인할 수 있다.

.select('td')[2].find('span','blind').text 으로 최종 원하는 결과를 얻을 수 있다.

# #chart_area > div.rate_info > table > tbody > tr:nth-child(1) > td:nth-child(3) > em
stock_rateinfo = soup.select('#chart_area > div.rate_info > table > tr')[0].select('td')[2].find('span','blind').text
print(stock_rateinfo)

 

블로그 이미지

Link2Me

,
728x90

서버 보안 설정 사항 중 몇가지를 적어둔다.

 

### 패스워드 잠금횟수 설정
vim /etc/pam.d/system-auth
auth        required       pam_tally2.so deny=5 unlock_time=120 no_magic_root account required pam_tally2.so no_magic_root reset
 
#account     required     pam_unix.so 다음 줄에 추가한다.
account     sufficient    pam_tally2.so
 
 
vim /etc/pam.d/password-auth
#auth        required      pam_tally2.so deny=5 unlock_time=120
auth        required      pam_tally2.so deny=5 unlock_time=120 no_magic_root account required pam_tally2.so no_magic_root reset
 
#account     required     pam_unix.so 다음 줄에 추가한다.
account     sufficient    pam_tally2.so
 
### /etc/hosts 파일 소유자 및 권한 설정
chown root /etc/hosts 
chmod 600 /etc/hosts
 
### /etc/(x)inetd.conf 파일 소유자 및 권한 설정
ls -al /etc/xinetd.conf
chown root /etc/xinetd.conf 
chmod 600 /etc/xinetd.conf 
 
# 파일이 존재하지 않으면
ls -alR /etc/xinetd.d/
chown -R root /etc/xinetd.d/ 
chmod -600 /etc/xinetd.d/ 
 
### /etc/syslog.conf 파일 소유자 및 권한 설정
ls -al /etc/rsyslog.conf
chown root /etc/rsyslog.conf
chmod 640 /etc/rsyslog.conf
 
### 정책에 따른 시스템 로깅 설정
vim /etc/rsyslog.conf
*.alert                                                 /dev/console
*.emerg   *
 
# rsyslog 서비스 재시작
systemctl restart rsyslog
 
 
### cron 파일 소유자 및 권한설정
ls -/usr/bin/crontab
chmod 750 /usr/bin/crontab
# crontab 파일 권한 확인
ls -al /usr/bin/crontab
 
# cron 관련 설정파일 소유자 및 권한 설정
chown root /etc/cron.deny
chmod 640 /etc/cron.deny
ls -al /etc/cron.deny
 
# 파일이 존재하면 아래 명령어 실행
ls /etc/cron.d/
chown root /etc/cron.d/cron.allow
chmod 640 /etc/cron.d/cron.allow
chown root /etc/cron.d/cron.deny
chmod 640 /etc/cron.d/cron.deny
 
 
### FTP 계정 shell 제한
cat /etc/passwd
# passwd 파일 내 로그인 쉘 설정이 /bin/false 가 아닌 경우 보안 설정
usermod -/bin/false ftp
 
 
### at 파일 소유자 및 권한 설정
ls -/usr/bin/at
chmod 0750 /usr/bin/at
 
chown root /etc/at.allow
chmod 640 /etc/at.allow
chown root /etc/at.deny
chmod 640 /etc/at.deny
 
# 파일 권한 및 사용자 확인
ls -al /etc/at.allow
ls -al /etc/at.deny
 
 
### SSH, SFTP 로그 기록 설정(상)
※ SSH
#1. /etc/profile 파일에 아래 설정 수정 및 추가
vim /etc/profile
...
function logging
{
    stat="$?"
    cmd=$(history|tail -1)
    if [ "$cmd" != "$cmd_old" ]; then
    logger -p local1.notice "[2] STAT=$stat"
    logger -p local1.notice "[1] PID=$$, PWD=$PWD, CMD=$cmd"
    fi
    cmd_old=$cmd
}
trap logging DEBUG
 
#2. rsyslog.conf 파일에 아래 설정 추가
vim /etc/rsyslog.conf
...
local1.*                                                /var/log/secure
 
#3. 변경된 환경변수 적용
source /etc/profile
 
#4. rsyslog 서비스 재시작
systemctl restart rsyslog
 
 
# sshd_config 파일 내 sftp 설정에 "-f AUTHPRIV -l VERBOSE" 설정 추가
vim /etc/ssh/sshd_config
 
# shift + g 를 눌러 최하단으로 이동한다.
 
#Subsystem sftp /usr/libexec/openssh/sftp-server
# 아래와 같이 추가한다.
Subsystem sftp /bin/false
Match LocalPort 2222  
      ForceCommand internal-sftp -f AUTHPRIV -l VERBOSE
 
# sshd 서비스 재시작
systemctl restart sshd
 
### root 계정 원격 접속 제한(상)
/PermitRootLogin 으로 찾아서
PermitRootLogin no 로 변경한다.
 
# sshd 서비스 재시작
systemctl restart sshd
# 만약의 경우를 대비하여 콘솔창을 닫으면 안된다.
# sftp 로 root 접속하는 경우 접속이 안된다.
 

 

 

 

'Web 프로그램 > Web Security' 카테고리의 다른 글

HTML Purifier 사용법 예시  (0) 2023.02.28
remote IP address  (0) 2021.03.24
파일 다운로드 공격 방지  (0) 2019.07.05
파일 업로드 공격 방지  (0) 2019.06.21
Cross-Site Scripting (XSS) 방지  (0) 2019.06.20
블로그 이미지

Link2Me

,
728x90

Web Root 디렉토리에서 설치를 위해 아래와 같이 실행한다.

# vendor/ezyang/htmlpurifier/library/ 에 자동 설치된다.
composer require ezyang/htmlpurifier

 

HTML Purifier는 해외의 보안 전문가들로부터 철저하게 검증받은 필터링 라이브러리이다.
페이지 표시할 때마다 매번 필터링하지 말고, DB에 저장하기 전에 한 번만 필터링한다.
게시물 제목이나 댓글처럼 HTML이 필요없는 경우 그냥 htmlspecialchars 또는 strip_tags 사용한다.
첨부파일 업로드를 사용한 XSS 공격은 게시물 내용과 별도로 막아줘야 한다.

 

https://stackoverflow.com/questions/2677578/php-html-purifier-hello-world-world-tutorial-striptags

 

PHP - HTML Purifier - hello w<o>rld/world tutorial striptags

I am just looking into using HTML Purifier to ensure that a user-inputed string (that represents the name of a person) is sanitized. I do not want to allow any html tags, script, markup etc - I just

stackoverflow.com

 

<?php
include_once( 'htmlpurifier/htmlpurifier/library/HTMLPurifier.auto.php');
?>
<form method="post">
<input type="text" name="fname" placeholder="first name"><br>
<input type="text" name="lname" placeholder="last name"><br>
<input type="submit" name="submit" value="submit">
</form>
        
<?php
if(isset($_POST['submit']))
{
    $fname=$_POST['fname'];
    $lname=$_POST['lname'];
    
    $config = HTMLPurifier_Config::createDefault();
    $purifier = new HTMLPurifier($config);
    $fname = $purifier->purify($fname);
    
    $config = HTMLPurifier_Config::createDefault();
    $purifier = new HTMLPurifier($config);
    $lname = $purifier->purify($lname);
 
    echo "First name is: ".$fname."<br>";
    echo "Last name is: ".$lname;
}
?>

 

본격적으로 사용해 보기 위해서 자료를 검색하고 분석하는 중이다.

'Web 프로그램 > Web Security' 카테고리의 다른 글

CentOS 7 서버 보안  (0) 2023.03.03
remote IP address  (0) 2021.03.24
파일 다운로드 공격 방지  (0) 2019.07.05
파일 업로드 공격 방지  (0) 2019.06.21
Cross-Site Scripting (XSS) 방지  (0) 2019.06.20
블로그 이미지

Link2Me

,
728x90

일급 객체(First Class citizen)
1. 변수(variable)에 담을 수 있다.
2. 파라미터로 전달할 수 있다.
3. 함수의 반환 값으로 사용할 수 있다.

 

/***
 * first class citizen(1급 객체)
 * 변수에 할당(assignment)할 수 있다.
 * 다른 함수를 인자(argument)로 전달 받는다.
 * 다른 함수의 결과로서 리턴될 수 있다.
 */
 
function mul(num1, num2) {
    return num1 * num2;
}
 
/***
 * func는 매개변수, 이름은 func이건 mul이건 상관없음
 * 매개변수(parameter)로 func를 받았고, 함수(func)를 리턴하기 때문에 고차함수
 * @param func : func()의 매개변수가 2개라면 mulNum()의 매개변수도 2개이여야함
 * @param number1
 * @param number2
 * @returns {*}
 */
function mulNum(func, number1, number2) {
    return func(number1, number2);
}
 
// 전달인자(argument)로 받은 함수인 mul은 콜백함수
let result = mulNum(mul, 34);
console.log(result);
 
// 다른 함수의 결과로 리턴 될 수 있다.
function add(num1) {
    return function (num2) {
        return num1 + num2;
    }
}
 
const rs_add = add(3)(4);
console.log(rs_add);

 

 

'React > morden javascript' 카테고리의 다른 글

JavaScript Object.assign()  (0) 2023.10.17
Javascript 클로저(Closure)  (0) 2023.10.06
[ES2020] optional chaining  (0) 2022.12.28
[ES6] 비구조화 할당(destructuring assignment)  (0) 2022.12.28
Javascript Array.filter 사용 예제  (0) 2022.12.27
블로그 이미지

Link2Me

,
728x90

Optional Chaining은 ES2020에서 등장한  '?.' 연산자는 체인으로 이루어진 각 참조가 유효한지 명시적으로 검증하지 않고 연결된 객체 체인 내에 깊숙이 위치한 속성 값을 읽을 수 있다.

Node.js의 경우 버전 14부터 지원한다.

 

참조가 null 혹은 undefined여도 에러를 출력하지 않고 undefined 값을 리턴한다.

함수 호출시에도 마찬가지로 값이 없다면 undefined를 리턴한다.

 

옵셔널 체이닝 앞의 변수는 꼭 선언되있어야 한다.

const obj = {
    name'Alice',
    cat: {
        name'Dinah'
    }
};
 
const dogName = obj.dog?.name;
console.log(dogName); // output: undefined
 
console.log(obj.someNonExistentMethod?.()); // output: undefined
 
let arr = [12345]
console.log(arr?.[4]);
console.log(arr?.[5]); // undefined
arr[5= 6;
console.log(arr?.[5]);
 
// optional chaining은 값을 읽고 삭제할때에는 사용할 수 있지만
// 값을 할당할 때는 사용할 수 없다.
let user = {
    age: 35
}
//user?.name = '홍길동'; // undefined = '홍길동'
// undefined가 반환되었을 때 null 병합 연산자(??)를 사용해 기본 값을 할당할 수 있다.
user.name = user?.name ?? '홍길동';
console.log(user.name, user.age);

 

optional chaining 이 브라우저에 따라 동작이 안될 수 있으니 반드시 확인해야 한다.

블로그 이미지

Link2Me

,
728x90

ES6부터 비구조화 할당(Destructuring Assignment)이라는 새로운 자바스크립트 표현식이 추가되었다.

비구조화할당이란 배열이나 객체의 속성 혹은 값을 해체하여 그 값을 변수에 각각 담아 사용하는 자바스크립트 표현식이다.

 

/***
 * 비구조화 할당의 기본 구조는 좌측에는 변수, 우측에는 해당 변수에 넣어지는 값을 표현한다.
 * 배열의 경우 []를 사용하고, 객체의 경우 {}를 사용한다.
 
 const [변수명1, 변수명2, 변수명3] = [값1, 값2, 값3];
 const [변수명1, 변수명2, 변수명3 = 기본값] = [값1, 값2];
 
 const 배열명 = [값1, 값2, 값3];
 const [변수명1, 변수명2, 변수명3] = 배열명;
 
 const {변수명1,변수명2,변수명3} = {
     속성명1: 값1,
     속성명2: 값2,
     속성명3: 값3
 };
 
*/
 
const arr = [1];
const [a = 10, b = 20= arr;
// 배열 값이 있으면 변수 기본 값은 무시한다.
console.log({ a, b });
 
const fruits = ['apple''banana''cherry''peach']
const [apple, ...etc] = fruits;
console.log(apple);
console.log(etc);

// JSON 객체에서는 우측의 key 값이 좌측의 변수명과 매칭된다.
const { cat, dog, tiger } = {
    cat: 'CAT',
    dog: 'DOG',
    tiger: 'TIGER'
};
 
console.log(cat);
console.log(dog);
console.log(tiger);

전개 연산자(...)를 이용하면 하나의 변수를 할당하고 나머지 값들을 모두 넣어준다.

전개 연산자를 사용하려면 반드시 가장 마지막 요소에 넣어줘야 한다.

나머지 요소의 오른쪽 뒤에 쉼표가 있으면 SyntaxError가 발생한다.
var [a, ...b,] = [1, 2, 3];

 

 

const arr2 = [123];
const [first, ...rest1] = arr2;
console.log(rest1); // 컴마 개수 이후의 모든 나머지 [2,3]
const [aa, bb, cc, ...rest2] = arr2;
console.log(rest2); // 빈 배열 []
 
const obj = { age: 36name'이순신' };
const { age, name } = obj;
console.log({ age, name });
const { ao, bo } = obj; // 객체에 없는 변수를 할당하면??
console.log({ao, bo}); // { ao: undefined, bo: undefined }
 
const obj2 = { age2: undefined, name2: null, grade2: 'A' };
const { age2 = 0, name2 = 'noName', grade2 = 'F' } = obj2;
console.log({ age2, name2, grade2 }); // { age2: 0, name2: null, grade2: 'A' }
// undefined 인 경우에만 기본값으로 대체된다.

 

 

블로그 이미지

Link2Me

,
728x90

Array filter 메서드는 조건을 만족하는 요소만 모아서 새로운 배열을 반환한다.

const arr = [1581012151620];
 
/***
* Array.prototype.filter() 메서드는 조건을 만족하는 요소만 모아서 새로운 배열을 리턴
 */
const rs1 = arr.filter(x => x % 5 == 0); // 조건 : 5의 배수
console.log(rs1);
 
// filter 메서드 대신 find 메서드를 사용하면 어떤 결과를 반환할까?
console.log(arr.find(x => x % 5 == 0)); // find 메서드는 단 하나의 요소만 리턴
 
console.log(arr.filter((item) => item >= 12)); // 조건 : 12보다 큰 수
 
const users = [
    { name'지민', salary : 7000000 },
    { name'철수', salary : 2500000 },
    { name'재석', salary : 3200000 },
    { name'종민', salary : 4600000 },
];
 
testJson = JSON.stringify(users);
const newJson = JSON.parse(testJson).filter(function(item){
    console.log(item);
    return item.name == "종민";
});
console.log(newJson);
 
// 화살표 함수로 조건을 쉽게 사용 가능
const newUser = users.filter(item => item.name == "종민");
console.log(newUser);
 
const users2 = {
    class : "1-1",
    subject : "English",
    scores : [
        { name : "길동", score : 60 },
        { name : "재섭", score : 80 },
        { name : "현수", score : 100 },
        { name : "희성", score : 70 },
        { name : "광수", score : 40 },
        { name : "혜리", score : 90 }
    ]
};
 
// 점수가 80점 이상인 학생들
const rs3 = users2.scores.filter(item => item.score >= 80);
console.log(rs3);
 
// 점수가 70 ~ 90 사이인 학생들
const rs4 = users2.scores.filter(item => item.score >= 70 && item.score <= 90);
console.log(rs4);

 

 

 

블로그 이미지

Link2Me

,
728x90

Definition and Usage
map() creates a new array from calling a function for every array element.
map() calls a function once for each element in an array.
map() does not execute the function for empty elements.
map() does not change the original array.

map을 이용하면 원 배열을 변경하지 않고 새로운 배열을 만든다.

map은 forEach와 마찬가지로 Array 각 요소를 순회하며 callback 함수를 실행한다. callback에서 return 되는 값을 배열로 반환한다.

 

const arr = [123];
const new_arr = new Array(123);
 
console.log('arr',arr);
console.log('new_arr',new_arr);
console.log(typeof arr === "object");
console.log(Object.values(arr));
 
// Array map() 함수를 이용하여 배열의 요소들을 변경하여 새로운 배열로 리턴
const arr1 = [12345];
 
const result1 = arr1.map(x => x + 1);
console.log('result1', result1);
 
const result_map = arr1.map(x => x * x);
console.log('result_map',result_map);
 
const result_old = [];
for (i = 0; i < arr1.length; i++) {
   result_old.push(arr1[i] * arr1[i]);
}
console.log('result_old',result_old);
 
const result3 = arr1.map(function (x,index){
   console.log(`arr1[${index}] = ${x}`);
   return x*3;
});
console.log('result3',result3);
 
 
const users = [
   { name'지민', age: 22 },
   { name'철수', age: 32 },
   { name'재석', age: 21 },
   { name'종민', age: 35 },
];
 
// 특정 요소만 재정의
const newUsers = users.map(user => {
   if (user.name === '철수') {
      return { ...user, age: 38 };
   }
   return { ...user };
});
console.log(newUsers);
// keys() is not supported in Internet Explorer 11 (or earlier).
console.log(Object.keys(newUsers));
console.log(Object.values(newUsers));
// The entries() method is not supported in Internet Explorer 11 (or earlier).
console.log(Object.entries(newUsers));
for (const [key, value] of Object.entries(newUsers)) {
   console.log(key, value);
}
 
/***
 * 자바스크립트엔 여덟 가지 자료형(숫자형, bigint형, 문자형, 불린형, 객체형, Null형, Undefined형, 심볼형)이 있다.
 * const 키워드에 객체를 할당하면 당연히 객체를 가리키는 주소 값이 할당된다. 그리고 이 주소 값은 불변이다.
 * 객체는 참조값을 통해 관리되고 있어 객체의 속성 값이 재할당 되더라도 오류가 발생되지 않는다.
 */
users[0].age = 18// const 키워드로 선언된 객체의 속성값은 재할당할 수 있다.
console.log(users);

 

 

 

블로그 이미지

Link2Me

,
728x90

"카카오 맵 키워드로 장소검색하고 목록으로 표출하기" 로 검색된 결과 URL 이다.

https://apis.map.kakao.com/web/sample/keywordList/

위 UTL 예시에 나온 코드로는 부족하므로 클러스터링 등 내용을 더 찾아서 살을 붙여야 한다.

 

화면 구성을 위해서 아래와 같이 HTML 을 변경한다.

아래 HTML 코드에서 카카오맵 API Key는 삭제했다.

<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>카카오지도</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/mdb.lite.css">
<link rel="stylesheet" href="/css/jquery-ui.css">
<link rel="stylesheet" href="/css/kakaomap.css"/>
</head>
<body>
<div class="card">
<div class="my-0 mx-2">
    <div class="form-check form-check-inline">
        <button type="button" class="btn btn-outline-primary btn-sm px-2" id="searchToggle">OFF</button>
    </div>
 
    <div class="form-check form-check-inline">
        <p class="fs-4">반경</p>
    </div>
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis1" value="0.5" checked />
      <label class="form-check-label" for="dis1">0.5</label>
    </div>
 
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis2" value="1.0" />
      <label class="form-check-label" for="dis2">1</label>
    </div>
 
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis3" value="1.5" />
      <label class="form-check-label" for="dis3">1.5</label>
    </div>
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis4" value="2.0" />
      <label class="form-check-label" for="dis4">2</label>
    </div>
 
    <div class="form-check form-check-inline">
        <button type="button" class="btn btn-outline-primary btn-sm px-2" id="curLoc">현위치</button>
    </div>
 
</div>
<div class="mt-1 mb-2 mx-2">
    <div class="map_wrap">
        <div id="map" style="width:100%;height:800px;position:relative;overflow:hidden;"></div>
 
        <div id="menu_wrap" class="bg_white">
            <div class="option">
                <div class="form-check form-check-inline">
                    <form onsubmit="searchPlaces(); return false;">
                        <input type="text" value="" id="keyword" size="15"> 
                        <button type="submit">&nbsp;검색</button>
                    </form>
                    <img src="./marker/icon_eraser.png" alt="clear" height="30" width="30" style="margin-left: 10px;" id="eraser">
                </div>
            </div>
            <div id="datacount"></div>
            <hr>
            <ul id="placesList"></ul>
            <div id="pagination"></div>
        </div>
        <div id="dialog" title="지도 Infowindow"></div>
    </div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script type="text/javascript" src="/js/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/mdb.lite.min.js"></script>
<script type="text/javascript" src="/js/jquery-ui.js"></script>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=&libraries=services,clusterer,drawing"></script>
<script type="text/javascript" src="Map.js"></script>
<script>
$("#searchToggle").click(function(){
    // 지도 검색창 ON/OFF
    if($("#menu_wrap").css("display")=="none"){
        $("#menu_wrap").show();
        $("#searchToggle").text('OFF');
    } else {
        $("#menu_wrap").hide();
        $("#searchToggle").text('ON');
    }
});
 
$("input[name='radius']").change(function(){
    searchPlaces();
});
$("#curLoc").click(function(){
    // 현재 지도의 중심좌표 구하기
    searchPlaces();
});
 
$("#eraser").click(function(){
    $("#keyword").val('');
});
</script>
</body>
</html>

 

Map.js

변수는 let 과 const 로 전부 변경하고, Ajax 로 PHP와 연동된 DB 자료를 가져다가 출력하는 걸 만든다.

    // 마커를 담을 배열입니다
    let markers = [];
 
    const mapContainer = document.getElementById('map'), // 지도를 표시할 div
    mapOption = {
        center: new kakao.maps.LatLng(37.566826126.9786567), // 지도의 중심좌표
        level: 12 // 지도의 확대 레벨
    };
 
    // 지도를 생성합니다
    const map = new kakao.maps.Map(mapContainer, mapOption);
 
    // 마커 클러스터러를 생성합니다
    const clusterer = new kakao.maps.MarkerClusterer({
        map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
        averageCenter: true// 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
        minLevel: 6 // 클러스터 할 최소 지도 레벨
    });
 
    // 일반 지도와 스카이뷰로 지도 타입을 전환할 수 있는 지도타입 컨트롤을 생성합니다
    const mapTypeControl = new kakao.maps.MapTypeControl();
 
    // 지도에 컨트롤을 추가해야 지도위에 표시됩니다
    // kakao.maps.ControlPosition은 컨트롤이 표시될 위치를 정의하는데 TOPRIGHT는 오른쪽 위를 의미합니다
    map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT);
 
    // 지도 확대 축소를 제어할 수 있는  줌 컨트롤을 생성합니다
    const zoomControl = new kakao.maps.ZoomControl();
    map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHTBOTTOM);
 
    // 지도가 이동, 확대, 축소로 인해 중심좌표가 변경되면 마지막 파라미터로 넘어온 함수를 호출하도록 이벤트를 등록합니다
    let getLat = null;
    let getLng = null;
    kakao.maps.event.addListener(map, 'center_changed'function() {
        // 지도의  레벨을 얻어옵니다
        const level = map.getLevel();
        // 지도의 중심좌표를 얻어옵니다
        const latlng = map.getCenter();
        console.log('중심 위도: '+latlng.getLat() + ', 경도: ' + latlng.getLng());
        getLat = latlng.getLat();
        getLng = latlng.getLng();
    });
 
    // 장소 검색 객체를 생성합니다
    const ps = new kakao.maps.services.Places();
 
    // 검색 결과 목록이나 마커를 클릭했을 때 장소명을 표출할 인포윈도우를 생성합니다
    const infowindow = new kakao.maps.InfoWindow({zIndex:1});
 
    // 키워드로 장소를 검색합니다
    searchPlaces();
 
    // 키워드 검색을 요청하는 함수입니다
    function searchPlaces() {
        let keyword = document.getElementById('keyword').value;
        let radius = $("input[name='radius']:checked").val();
 
        $.ajax({
            url : 'ajaxMap.php',
            type : 'GET',
            data : { 'msg': keyword, 'radius':radius, 'lat':getLat, 'lng':getLng },
            dataType: 'json',
            success : function(json) {
                if(json.length == 0) {
                    alert('검색 데이터가 없습니다.');
                    return;
                }
 
                console.log('데이터 갯수 :: ' + json.length);
                displayPlaces(json);
 
                // 클러스터에 마커들을 추가합니다
                clusterer.addMarkers(markers);
 
                var pagination = [ '1''2''3' ];
                displayPagination(pagination);
            },
            error : function(xhr, status, error){
                alert("에러코드 : " + xhr.status); //요청에 실패하면 에러코드 출력
            },
            complete : function() {
                
            }
        });
    }
 
    // 장소검색이 완료됐을 때 호출되는 콜백함수 입니다
    function placesSearchCB(data, status, pagination) {
        if (status === kakao.maps.services.Status.OK) {
            // 정상적으로 검색이 완료됐으면 검색 목록과 마커를 표출합니다
            displayPlaces(data);
 
            // 페이지 번호를 표출합니다
            displayPagination(pagination);
        } else if (status === kakao.maps.services.Status.ZERO_RESULT) {
            alert('검색 결과가 존재하지 않습니다.');
            return;
        } else if (status === kakao.maps.services.Status.ERROR) {
            alert('검색 결과 중 오류가 발생했습니다.');
            return;
        }
    }
 
    // 검색 결과 목록과 마커를 표출하는 함수입니다
    function displayPlaces(places) {
        let listEl = document.getElementById('placesList'),
        menuEl = document.getElementById('menu_wrap'),
        fragment = document.createDocumentFragment(),
        bounds = new kakao.maps.LatLngBounds(),
        listStr = '';
 
        // 검색 결과 목록에 추가된 항목들을 제거합니다
        removeAllChildNods(listEl);
 
        // 지도에 표시되고 있는 마커를 제거합니다
        removeMarker();
 
        const cntEl = document.getElementById('datacount');
        cntEl.innerText = '검색 개수 : ' + places.length;
 
 
        for (let i=0; i<places.length; i++) {
 
            let color_marker = "marker-icon-blue.png";
 
            if (places[i].yn == 0)  { // 없으면 => 빨간색
                color_marker = "marker-icon-red.png";
                if(places[i].isbiz > 0) {
                    color_marker = "marker-icon-green.png";
                }
            } else {
                color_marker = "marker-icon-blue.png";
                if(places[i].isbiz > 0) {
                    color_marker = "marker-icon-orange.png";
                }
            }
 
            // 마커를 생성하고 지도에 표시합니다.
            let idx = places[i].idx; // 절대 var 변수를 선언하면 안된다. 반드시 let 변수를 선언해야 제대로 동작되더라. 개삽질을 엄청했다.
            let title = places[i].idx;
            let placePosition = new kakao.maps.LatLng(places[i].latitude, places[i].longitude);
            let marker = addMarkers(placePosition, color_marker, title);
            let itemEl = getListItem(i, places[i], color_marker); // 검색 결과 항목 Element를 생성합니다
 
            // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 LatLngBounds 객체에 좌표를 추가합니다
            bounds.extend(placePosition);
 
            // 마커와 검색결과
            // 항목에 mouseover 했을때 해당 장소에 인포윈도우에 장소명을 표시합니다. mouseout 했을 때는 인포윈도우를 닫습니다
            (function(marker, title) {
                kakao.maps.event.addListener(marker, 'mouseover'function() {
                    displayInfowindow(marker, title);
                });
 
                kakao.maps.event.addListener(marker, 'mouseout'function() {
                    infowindow.close();
                });
 
                itemEl.onmouseover =  function () {
                    displayInfowindow(marker, title);
                };
 
                itemEl.onmouseout =  function () {
                    infowindow.close();
                };
 
                // 마커에 클릭 이벤트를 등록한다.
                kakao.maps.event.addListener(marker, 'click'function() {
                    //map.panTo(placePosition); // 지도 중심을 부드럽게 이동시킨다.
                    console.log('idx:' + idx);
                    custom_infowindow(idx);
 
                });
 
            })(marker, places[i].bldNM);
 
            fragment.appendChild(itemEl);
        }
 
        // 검색결과 항목들을 검색결과 목록 Elemnet에 추가합니다
        listEl.appendChild(fragment);
        menuEl.scrollTop = 0;
 
        // 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
        map.setBounds(bounds);
    }
 
    function custom_infowindow(idx){
        $('#dialog').load('infowindow.php?do='+idx, function() {
            $(this).dialog({
                autoOpen : true,
                height : 'auto',
                width : 'auto',
                position: { my: "center", at: "center", of: window },
                title : 'XX정보',
                buttons : {
                    "닫기" : function() {
                        $(this).dialog("close");
                    }
                }
            });
        });
 
    }
 
    // 검색결과 항목을 Element로 반환하는 함수입니다. 위도, 경도, 번호, 건물명, 도로명주소, 지번주소
    function getListItem(index, places, _color) {
        let el = document.createElement('li'),
        itemStr = '<span style="float:left;position:absolute;width:33px; height:31px;margin:10px 0 0 10px;"><img width="31" height="31" src="./marker/' + _color + '"></span>' +
                  '<div class="info">' +
                  '   <h5>' + places.bldNM + '</h5>';
 
        if (places.stAddress) {
            itemStr += '   <span>' + places.stAddress + '</span>' +
                       '   <span class="jibun gray">' +  places.jiAddress  + '</span>';
        } else {
            itemStr += '   <span>' +  places.jiAddress  + '</span>';
        }
 
        itemStr     += '  <span class="tel">' + places.idx  + '</span>' +
                       '</div>';
 
        el.innerHTML = itemStr;
        el.className = 'item';
 
        return el;
    }
 
 
    function addMarkers(position, _color, title) {
        const imageSrc = './marker/' + _color;
        const imageSize = new kakao.maps.Size(3333);
        const imageOptions = {
            offset: new kakao.maps.Point(2769)
        };
 
        const markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOptions)
        // 마커를 생성합니다
        const marker = new kakao.maps.Marker({
            position: position,
            //title: title,
            image: markerImage
        });
 
 
        marker.setMap(map); // 마커가 지도 위에 표시되도록 설정합니다
        markers.push(marker); // 생성된 마커를 배열에 추가합니다
 
        return marker;
    }
 
    // 지도 위에 표시되고 있는 마커를 모두 제거합니다
    function removeMarker() {
        //clusterer.removeMarkers(markers);
        clusterer.clear(); // 추가된 모든 마커 삭제
        for ( let i = 0; i < markers.length; i++ ) {
            markers[i].setMap(null);
        }
        markers = [];
    }
 
    // 검색결과 목록 하단에 페이지번호를 표시는 함수입니다
    function displayPagination(pagination) {
        let paginationEl = document.getElementById('pagination'),
            fragment = document.createDocumentFragment(),
            i;
 
        // 기존에 추가된 페이지번호를 삭제합니다
        while (paginationEl.hasChildNodes()) {
            paginationEl.removeChild (paginationEl.lastChild);
        }
 
        for (i=1; i<=pagination.last; i++) {
            let el = document.createElement('a');
            el.href = "#";
            el.innerHTML = i;
 
            if (i===pagination.current) {
                el.className = 'on';
            } else {
                el.onclick = (function(i) {
                    return function() {
                        pagination.gotoPage(i);
                    }
                })(i);
            }
 
            fragment.appendChild(el);
        }
        paginationEl.appendChild(fragment);
    }
 
    // 검색결과 목록 또는 마커를 클릭했을 때 호출되는 함수. 인포윈도우에 장소명을 표시합니다
    function displayInfowindow(marker, title) {
        const content = '<div style="padding:5px;z-index:1;">' + title + '</div>';
 
        infowindow.setContent(content);
        infowindow.open(map, marker);
    }
 
    // 검색결과 목록의 자식 Element를 제거하는 함수입니다
    function removeAllChildNods(el) {
        while (el.hasChildNodes()) {
            el.removeChild (el.lastChild);
        }
    }
 
 

 

백엔드 PHP 코드는 여기에는 적지 않는다.

화면에 뿌리는 marker 의 개수와 성능은 백엔드 Query를 어떻게 구현하느냐에 따라 달라진다.

가능하면 SELECT 문의 결과를 최소로 하여 원하는 결과를 찾을 수 있게 구현한다.

 

 

 

 

 

 

'React > morden javascript' 카테고리의 다른 글

Javascript Array.filter 사용 예제  (0) 2022.12.27
Javascript Array.map 사용 예제  (2) 2022.12.27
[Javascript] 정규표현식  (0) 2022.10.08
[Javascript] _cloneDeep()  (0) 2022.10.05
[ES6] Spread 연산자  (0) 2022.10.02
블로그 이미지

Link2Me

,
728x90

Node.js 에서 암호화/복호화하는 예제를 구현 테스트하고 적어둔다.

같은 언어에서 암호화하고 복호화하는 것은 전혀 문제가 없었다.

이기종 언어에서 테스트를 하니까 안되어서 원인 찾느라고 개고생을 했다.

원인은 const iv = Buffer.alloc(16,0); 로 해주는 것을 찾아내는 것이 문제였다.

아래 코드로 REST API 에서 AES256 암호화한 값을 제대로 전달하고, 복호화하는데도 문제가 없다는 걸 확인했다.

 

모듈 설치

npm install crypto --save

 

모듈을 불러와서 사용하는 방법으로 코드를 구현했다.

파일명 : aes_crypto.js

const crypto = require('crypto'); // 암호화 모듈 선언
 
// 암호화 AES256
function AES_encrypt(data, key) {
    const iv = Buffer.alloc(16,0); // 16비트
    const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
    let encryptedText = cipher.update(data, 'utf8''base64');
    encryptedText += cipher.final('base64');
    return encryptedText;
}
 
// 복호화 AES256
function AES_decrypt(data, key) {
    const iv = Buffer.alloc(16,0); // 16비트
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let decryptedText = decipher.update(data, 'base64''utf8');
    decryptedText += decipher.final('utf8');
    return decryptedText;
}
 
module.exports = { AES_encrypt, AES_decrypt}
// Require()함수는 module.exports를 리턴한다.
 

 

사용 예제

key 를 함수에 포함할까 하다가 별도로 호출하는 방법을 택했다.

key 길이는 반드시 32비트 이어야 한다. 아래 key는 임의로 생성한 키다.

AES 암호화/복호화 함수가 포함된 파일을 require 로 호출하고 aes 로 변수에 할당했다.

// crypto_test.js
const aes = require('./aes_crypto');
 
// 암복호화 KEY
const key = 'Qsj23missdaxX2BjyskV6bs#adada6ds'// 32비트
 
// 암복호화 테스트
let plainText = '내 이름은 홍길동';
let encryptedText = aes.AES_encrypt(plainText, key);
let decryptedText = aes.AES_decrypt(encryptedText, key);
 
console.log('텍스트 : ', plainText);
console.log('암호화 : ', encryptedText);
console.log('복호화 : ', decryptedText);

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

변수

넌적스(nunjucks)에서 변수는 {{ }} 로 감싼다.

변수를 선언할 때는 {% set 변수 = '값' %}를 사용한다.

 

반복문

<ul>
  {% set fruits = ['사과''배''오렌지'' 바나나''복숭아'] %}
  {% for item in fruits %}
  <li>{{item}}</li>
  {% endfor %}
</ul>

 

조건문

조건문은 {% if 변수 %} {% elif %} {% else %} {% endif %}로 이루어져 있다.

{% if isLoggedIn %}
<div>로그인 되었다.</div>
{% else %}
<div>로그인 필요하다.</div>
{% endif %}
 
{% if fruit === 'apple' %}
<p>사과</p>
{% elif fruit === 'banana' %}
<p>바나나</p>
{% elif fruit === 'orange' %}
<p>오렌지</p>
{% else %}
<p>모두 아님</p>
{% endif %}

 

Layout

{% include "header.html" %}
 
{% include "footer.html" %}
 
 
{% extends 'layout.html' %}
 
{% block content %}
{% endblock %}
 
{% block script %}
<script src="/main.js"></script>
{% endblock %}

Layout 에 대한 자세한 사항은 https://mozilla.github.io/nunjucks/templating.html 를 참조하면 된다.

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

제목과 같은 메시지가 출력되어 로그 확인을 해본 결과 패스워드 인식이 제대로 안되는 걸 확인했다.

CentOS 7 .env 환경변수 파일을 만들고 패스워드를 확인해보니 #이 들어간 경우 패스워드를 뒷부분을 주석으로 인식한다는 걸 확인했다. 패스워드 만들 때 주의를 해야 할 사항일 수도 있겠더라.

 

const express = require('express');
const mysql = require('mysql');
const path = require('path');
 
const router = express.Router();
 
require('dotenv').config({ path: '.env'})
 
router.get('/', (req, res,next) => {
   //res.send('home 이후 url');
    console.log(process.env.DB_HOST);
    console.log(process.env.DB_USERNAME);
    console.log(process.env.DB_PASSWORD);
    console.log(process.env.DBNAME);
    res.render('index.html');
});
 
router.get('/login'function(req, res) {
    // Render login template
    res.render('login.html');
});
 
/*
var dbconn  = mysql.createPool({
  connectionLimit : 10,
  host     : 'localhost',
  user     : 'testuser',
  password : 'samplefull!#*',
  database : 'studydb'
});
// */
 
const dbconn  = mysql.createPool({
    connectionLimit : 10,
    host:process.env.DB_HOST,
    user:process.env.DB_USERNAME,
    password:process.env.DB_PASSWORD,
    database:process.env.DBNAME
});

 

주석으로 처리한 것과 같이  DB 접속 정보가 보이는데 보이지 않도록 아래와 같이 처리하는 걸 권장한다.

 

.env 환경변수 파일 내용

 

 

'node.js' 카테고리의 다른 글

Node.js AES 암호화 예제 (이기종 언어간 OK)  (0) 2022.12.06
Node.js nunjucks 기반 Layout  (0) 2022.11.19
[Node.js] ejs View, nunjucks View  (0) 2022.11.18
Node.js jQuery 설치 및 사용  (0) 2022.08.12
nodeJS 유틸리티 상속  (0) 2022.06.09
블로그 이미지

Link2Me

,
728x90

사용자 요청을 처리하는 라우팅 함수를 컨트롤러(Controller), 데이터베이스에 데이터를 저장하거나 조회하는 함수를 Model, 사용자에게 결과를 보여주기 위해 만든 파일을 View 라고 한다.

 

먼저 프로젝트 안에 있는 index.js 파일을 열고 뷰 엔진을 ejs 로 저정한다.

그러면 이제부터는 ejs 로 만든 뷰 템플릿을 사용해 응답을 보낼 수 있다.

app 객체의 set() 메소드는 속성을 설정하는 역할을 한다.

 

ejs 모듈 설치

npm install ejs --save

 

 

index.js

// Package Imports
const express = require('express');
const path = require('path');
 
// App Definition
const app = express();
const port = 3000;
 
// 템플릿 엔진 설정
app.set('views', __dirname + '/views');
app.set('view engine''ejs');
app.engine('html', require('ejs').renderFile);
/***
 * 코드 실행 : <% %>
 * 결과 출력 : <%= %>
 */
 

 

 

nunjucks 모듈 설치

https://mozilla.github.io/nunjucks/getting-started.html

 

Nunjucks

Getting Started User-Defined Templates Warning nunjucks does not sandbox execution so it is not safe to run user-defined templates or inject user-defined content into template definitions. On the server, you can expose attack vectors for accessing sensitiv

mozilla.github.io

위 사이트를 참조해서 npm install nunjucks --save 로 모듈 설치한다.

 

파일을 분리하여 작성하는 방법으로 테스트한 사항을 적어둔다.

index.js 또는 app.js 파일

const express = require('express');
const path = require('path');
let nunjucks = require('nunjucks'); // templating framework
 
// App Definition
const app = express();
 
// Routes 파일 불러오기
const homeRoute = require('./routes/home');
const aboutRoute = require('./routes/about')
const adminRoute = require('./routes/admin');
 
const port = 3000;
 
dotenv.config({ path: './.env'})
 
// 템플릿 엔진 설정
app.set("view engine""html");
nunjucks.configure('views', {
    autoescape: true,
    express: app
});
 
 
app.use('/',homeRoute);
app.use('/about',aboutRoute)
app.use('/admin', adminRoute);
 
app.listen(port,() => {
    console.log('Start On', port);
});
 

 

routes 폴더 밑에 home.js 파일

const express = require('express');
const router = express.Router();
 
 
router.get('/', (req, res,next) => {
   //res.send('home 이후 url');
   res.render('index.html');
});
 
router.get('/login'function(req, res) {
   // Render login template
   res.render('login.html');
});
 
module.exports = router;

여기서 주의할 사항은 res.render('/index.html'); 이라고 하면 절대 안된다.

router.get('/' function(req, res, next){}) 에서 경로 / 가 이미 지정되었기 때문이다.

 

 

routes 폴더 밑에 admin.js 파일

const express = require('express');
const nunjucks = require('nunjucks');
const logger = require('morgan'); // 로그 미들웨어 npm install morgan
// 로그 미들웨어 winston 은 파일로 남기기, DB에 저장, email, sms, 알림 메시지 사용 npm install winston
const bodyParser = require('body-parser');
const router = express.Router();
// 라우터 수준의 미들웨어
 
router.get('/', (req, res) => {
   // res.send('admin 이후 url');
   res.render('admin/index.html');
});
 
router.get('/products', ( _ , res) => {
   res.render( 'admin/products.html' ,
       { message : "hello" } // message 란 변수를 템플릿으로 내보낸다.
   );
});
 
 
module.exports = router;
 

 

 

 

 

views 폴더 내 index.html

파일의 변수를 인식하는 처리 그리고 Layout을 분리하는 구조까지는 처리하지 않았다.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Bootstrap Example</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-sm bg-light navbar-light">
    <ul class="navbar-nav">
        <li class="nav-item"><class="nav-link" href="/">Home</a></li>
        <li class="nav-item"><class="nav-link" href="/login">Login</a></li>
        <li class="nav-item"><class="nav-link" href="/register">Register</a></li>
        <li class="nav-item"><class="nav-link" href="/admin">Admin</a></li>
    </ul>
</nav>
 
<div class="container">
    <h1>Welcome to My Homepage</h1>
    <h2>Basic Table</h2>
    <p>The .table class adds basic styling (light padding and horizontal dividers) to a table:</p>
    <table class="table">
        <thead>
        <tr>
            <th>Firstname</th>
            <th>Lastname</th>
            <th>Email</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td>John</td>
            <td>Doe</td>
            <td>john@example.com</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>Moe</td>
            <td>mary@example.com</td>
        </tr>
        <tr>
            <td>July</td>
            <td>Dooley</td>
            <td>july@example.com</td>
        </tr>
        </tbody>
    </table>
</div>
 
</body>
</html>
 

 

위와 같은 구조로 동작된다는 걸 설명하기 위해서 테스트하고 적어둔 것이다.

 

views 폴더 하단에 layout 폴더를 추가하고 html 파일을 분리구조로 만드는 방법은 다음 게시글에 적어둘 것이다.

여기까지는 nunjucks 모듈을 설치하고 view 엔진으로 nunjucks 를 사용하는 간단 방법을 알아봤다.

블로그 이미지

Link2Me

,
728x90

React 에서 네이버 지도 API 를 이용하기 위해 간단 테스트하고 적어둔다.

먼저 https://www.ncloud.com/ 사이트에서http://localhost:3000 을 추가했다.

 

 

index.html 에 라이브러리 추가

// index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script type="text/javascript" src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=키추가하세요"></script>
  </body>
</html>
 

 

인증정보에서 Client ID 값을 복사하여 붙여넣기 한다.

 

네이버맵 Sample 구현

- https://navermaps.github.io/maps.js/docs/tutorial-2-Getting-Started.html 에 지도 DOM 요소 지정하기 <div id="map" style="width:100%;height:400px;"></div> 를 포함하도록 되어 있다. return 문 안에 div 태크를 추가한다.

- 지도 띄우는 코드는 useEffect Hook을 이용하여 샘플코드를 아래와 같이 추가한다.

  샘플에 나오는 var 변수를 사용해도 되고, const, let 등으로 변경하여 변수를 사용해도 된다.

- 최상위에 const { naver } = window; 를 반드시 추가 해준다.

 

// App.js
 
import './App.css';
import NaverMapEx1 from "./NaverMapEx1";
 
function App() {
  return (
    <div className="App">
      <NaverMapEx1 />
    </div>
  );
}
 
export default App;
 
 
 
// NaverMapEx1.js
import React, {useEffect, useState} from 'react';
 
const { naver } = window;
const NaverMapEx1 = () => {
    useEffect(() => {
        const container = document.getElementById("map"); // 지도를 표시할 div
 
        // let markerList = [];
        // const HOME_PATH = window.HOME_PATH || '.';
        const position = new naver.maps.LatLng(37.3849483127.1229117);
        const mapOptions = {
            center: position,
            zoom: 17,
            minZoom: 6,
            zoomControl: true,
            zoomControlOptions: {
                position: naver.maps.Position.TOP_RIGHT,
            },
        };
 
        const map = new naver.maps.Map(container, mapOptions);
 
        const markerOptions = {
            position: position.destinationPoint(9015),
            map: map,
            icon: {
                url:'https://navermaps.github.io/maps.js/docs/img/example/ico_pin.jpg',
                //size: new naver.maps.Size(50, 52),
                origin: new naver.maps.Point(00),
                anchor: new naver.maps.Point(2526)
            }
        };
 
        const marker = new naver.maps.Marker(markerOptions);
 
        console.log("loading navermap");
    },[]);
 
    return (
        <div>
            <div id="map" style={{width:'100%', height:'800px' }}></div>
        </div>
    );
};
 
export default NaverMapEx1;
 

 

JavaScript 버전에 나온 예제대로 icon url 을 적었더니 인식이 안된다.

React 에서 이미지 경로를 어떻게 인식시키는지 알아야만 해결이 될 듯 싶다.

 

간단하게 지도 띄우는 것은 가능한데 custom marker 구현과 infowindow까지 구현해봐야 편의성을 알 수 있을 듯 싶다.

'React > React' 카테고리의 다른 글

React 카카오맵 API 사용 샘플  (0) 2022.10.24
React useMemo & useCallback  (0) 2022.10.24
React useRef 사용 예제  (0) 2022.10.15
React useReducer  (0) 2022.10.13
React-bootstrap Header.js 테스트  (0) 2022.08.21
블로그 이미지

Link2Me

,
728x90

https://developers.kakao.com/ 에 접속하여 앱을 하나 생성하고 Web 사이트를 등록한다.

PC에서 구현하는 테스트 코드이므로 도메인은 http://localhost:3000 을 추가했다.

 

index.html 에 라이브러리 추가

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React 카카오맵</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=%REACT_APP_KAKAO_API%&libraries=services,clusterer,drawing"></script>
  </body>
</html>
 

 

root 폴더에 .env 파일을 생성하고 아래와 같이 Key 값을 저장한다.

JavaScript 키를 복사하여 .env 파일에 붙여넣기 한다.

 

 

카카오맵 Sample 구현

- https://apis.map.kakao.com/web/guide/ 에 지도를 만들 영역 만들기 <div id="map" style="width:500px;height:400px;"></div> 를 포함하도록 되어 있다. return 문 안에 div 태크를 추가한다.

- 지도 띄우는 코드는 useEffect Hook을 이용하여 샘플코드를 아래와 같이 추가한다.

  샘플에 나오는 var 변수를 사용해도 되고, const, let 등으로 변경하여 변수를 사용해도 된다.

- 최상위에 const { kakao } = window; 를 반드시 추가 해준다.

// App.js
import './App.css';
import KakaoMapEx1 from "./KakaoMapEx1";
 
function App() {
  return (
    <div className="App">
        <KakaoMapEx1 />
    </div>
  );
}
 
export default App;
 
 
// KakaoMapEx1.js
import React, {useEffect} from 'react';
 
const { kakao } = window;
 
const KakaoMapEx1 = () => {
    useEffect(() => {
        let markers = [];
        const container = document.getElementById("map"); // 지도를 표시할 div
 
        const options = {
            center: new window.kakao.maps.LatLng(37.566826126.9786567),
            level: 12// 지도의 확대 레벨
        };
 
        const map = new window.kakao.maps.Map(container, options);
 
        const markerPosition  = new kakao.maps.LatLng(37.365264512305174127.10676860117488);
        const marker = new kakao.maps.Marker({
            position: markerPosition
        });
        marker.setMap(map);
 
        // 버튼 클릭에 따라 지도 이동 기능을 막거나 풀고 싶은 경우에는 map.setDraggable 함수를 사용
        function setDraggable(draggable) {
            // 마우스 드래그로 지도 이동 가능여부를 설정
            map.setDraggable(draggable);
        }
 
        console.log("loading kakaomap");
    }, []);
 
    return (
        <div>
            <div id="map" style={{width:'100%', height:'800px',position:'relative', overflow:'hidden' }}></div>
        </div>
    );
};
 
export default KakaoMapEx1;

 

 

'React > React' 카테고리의 다른 글

React Naver Map API 사용 샘플  (2) 2022.10.25
React useMemo & useCallback  (0) 2022.10.24
React useRef 사용 예제  (0) 2022.10.15
React useReducer  (0) 2022.10.13
React-bootstrap Header.js 테스트  (0) 2022.08.21
블로그 이미지

Link2Me

,
728x90

함수형 컴포넌트는 랜더링 → component 함수 호출 모든 내부 변수 초기화의 순서를 거친다.

컴포넌트가 랜더링 될 때마다 value 라는 변수가 초기화된다.

useMemo를 사용하면 랜더링 → component 함수 호출 → Memoize 된 함수를 재사용하는 동작 순서를 거친다.

memoization이란 기존에 수행한 연산의 결과값을 어딘가에 저장해 두고 동일한 입력이 들어오면 재활용하는 프로그램 기법을 말한다.

 

object는 객체 타입으로 저장될 때 참조 주소값을 저장한다.

const location = { country: isKorea ? '한국' : '미국' }; 육안으로 보면 똑같지만 객체 타입으로 메모리에 저장될 때의 주소는 완전히 달라서 useMemo 를 사용하지 않으면 계속 rendering 될 수 있다.

React rerendering 하는 조건
- props가 변경되었을 때
- state가 변경되었을 때
- 부모 컴포넌트가 rendering 되었을 때
- forceUpdate() 를 실행하였을 때

useEffect, useCallback, useMemo의 모든 종속성은 참조 동일성을 확인한다.
useEffect() 함수는 두 번째 인자로 넘어온 의존 배열이 변경될 때만 첫 번째 인자로 넘어온 함수를 호출한다.

import React, {useEffect, useMemo, useState} from 'react';
 
const UseMemoEx = () => {
    const [number, setNumber] = useState(0);
    const [isKorea, setIsKorea] = useState(true);
 
    const location = useMemo(() => {
        return {
            country: isKorea ? '한국' : '미국'
        }
    }, [isKorea]);
 
    useEffect(() => {
        console.log('useEffect... 호출');
    }, [location]);
 
    return (
        <div>
            <h3>하루에 몇 끼 먹어요?</h3>
            <input
                type="number"
                value={number}
                onChange={(e) => setNumber(e.target.value)}
            />
            <p> 하루 식사 횟수 : {number}</p>
 
            <h3>어느 나라에 있어요?</h3>
            <p>나라: {location.country}</p>
            <button onClick={() => setIsKorea(!isKorea)}>토글</button>
        </div>
    );
};
 
export default UseMemoEx;

 

자식 컴포넌트에서 useEffect가 반복적으로 트리거되는 것을 막고 싶을 때 사용하자.

 

useMemo 는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.

The useCallback and useMemo Hooks are similar. 

The main difference is that useMemo returns a memoized value and useCallback returns a memoized function.

The React useCallback Hook returns a memoized callback function.
The useCallback Hook only runs when one of its dependencies update.

useCallback(fn, deps)
의존성 배열인 deps에 변경을 감지해야할 값을 넣어주게 되면 deps가 변경될 때마다 콜백 함수를 새로 생성한다.

 

 

let x = 3;
let y = 4;
 
// 자바스크립트에서 함수는 객체로 취급한다.
const add1 = (x, y) => x + y;
const add2 = (x, y) => x + y;
 
console.log(add1(x,y)); // 결과 7
console.log(add2(x,y)); // 결과 7
console.log(add1 === add2); // 참조변수 비교 결과 : false

 

 

 
import React, {useCallback, useState} from 'react';
 
function Light({ room, on, toggle }) {
    console.log({ room, on });
    return (
        <button onClick={toggle}>
            {room} {on ? "💡" : "⬛"}
        </button>
    );
}
 
Light = React.memo(Light);
 
const UseCallbackEx = () => {
    const [bedroom, setBedroom] = useState(false);
    const [kitchen, setKitchen] = useState(false);
    const [bath, setBath] = useState(false);
 
    const toggleBedroom = useCallback(() =>
        setBedroom(!bedroom), [bedroom]
    );
    const toggleKitchen = useCallback(() =>
        setKitchen(!kitchen), [kitchen]
    );
    const toggleBath = useCallback(() =>
        setBath(!bath), [bath]
    );
 
    return (
        <div>
            <Light room="주방" on={kitchen} toggle={toggleKitchen} />
            <Light room="욕실" on={bath} toggle={toggleBath} />
            <Light room="침실" on={bedroom} toggle={toggleBedroom} />
        </div>
    );
};
 
export default UseCallbackEx;

참조 : https://www.daleseo.com/react-hooks-use-callback/

 

React Hooks: useCallback 사용법

Engineering Blog by Dale Seo

www.daleseo.com

 

 

 

'React > React' 카테고리의 다른 글

React Naver Map API 사용 샘플  (2) 2022.10.25
React 카카오맵 API 사용 샘플  (0) 2022.10.24
React useRef 사용 예제  (0) 2022.10.15
React useReducer  (0) 2022.10.13
React-bootstrap Header.js 테스트  (0) 2022.08.21
블로그 이미지

Link2Me

,
728x90

import React, {useRef, useState} from 'react';
 
const UseRef = () => {
    const [renderer, setRenderer] = useState(0); // 숫자 증가시 화면 랜더링 발생
    const countRef = useRef(0); // 숫자 증가해도 화면 랜더링 없음
    let countVar = 0// 화면 랜더링 발생하면 값 초기화 발생
 
    const doRendering = () => {
        setRenderer(renderer + 1);
    }
 
    const increaseRef = () => {
        countRef.current = countRef.current + 1;
        console.log('ref:', countRef.current);
    }
 
    const increaseVar = () => {
        countVar = countVar + 1;
        console.log('var:', countVar);
    };
 
    return (
        <div>
            <p>Ref: {countRef.current}</p>
            <p>Var: {countVar}</p>
            <button onClick={doRendering}>랜더 UP</button>
            <button onClick={increaseRef}>Ref UP</button>
            <button onClick={increaseVar}>Var UP</button>
        </div>
    );
};
 
export default UseRef;

 

import logo from './logo.svg';
import './App.css';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import useWindowWidth from "./hooks/useWindowWidth";
import UseRef from "./components/UseRef";
 
function App() {
    const width = useWindowWidth();
    return (
        <div className="App">
            <header className="App-header">
                <img src={logo} className="App-logo" alt="logo"/>
                <UseRef />
                {width}
            </header>
        </div>
    );
}
 
export default App;

 

 

useEffect 와 useState를 잘못 사용하면 무한 랜더링이 발생할 수 있다.

useRef를 useEffect 와 사용하면 문제가 해결될 수 있으니 알아두자.

 

 

import React, {useRef, useState} from 'react';
 
const UseRefInput = () => {
    const [inputValue, setInputValue] = useState('');
    const useridInput = useRef();
 
    const onRest = () => {
        setInputValue('');
        useridInput.current.focus();
    };
 
    return (
        <div>
            <input type="text" name="userId"
                   ref={useridInput}
                   onChange={(e)=> setInputValue(e.target.value)}
                   placeholder="userID 입력"
                   value={inputValue} />
            <button onClick={onRest}>초기화</button>
            <p>input: {inputValue}</p>
        </div>
    );
};
 
export default UseRefInput;

 

 

'React > React' 카테고리의 다른 글

React 카카오맵 API 사용 샘플  (0) 2022.10.24
React useMemo & useCallback  (0) 2022.10.24
React useReducer  (0) 2022.10.13
React-bootstrap Header.js 테스트  (0) 2022.08.21
React CORS error 해결 방법  (0) 2022.08.20
블로그 이미지

Link2Me

,