오늘은 nginx 웹 서버를 셋팅하는 작업을 진행 할거다!

nginx 에대한 기본 설명을 좀 하고 셋팅 과정을 천천히 진행해보자!

 

nginx 는 아파치와 같은 웹서버중 하나이며, 가볍고 여러요청을 한번에 처리가 가능한 장점을 가진 웹서버이다.

동작이 단순하고 전달자 역할만 해서 동시접속에 특화가 되어있다! 구동방식은 event driven (비동기처리방식)인데

요청이 들어올시 어떤 동작을 해야하는 지만 알려주고 다른 요청을 처리하는 방식이다.

cpu와 관계없이 모든 입출력들을 전부 이벤트 리스너로 전달하기 때문에 흐름이 끊기지 않고 응답이 빠르게 진행되어 1개의 프로세스로 보다 더빠른 작업을 가능하게 한다. 여기서 웹서버는 무엇이냐? 

웹서버의 역할은 html, css 자바스크립트, 이미지 와 같은 정적인 정보를 사용자 에게 전송해주는 역할을 한다.

또한 리버스 프록시의 역할도 한다! 리버스 프록시는 기존의 포워드 프록시가 클라이언트 앞단에서 요청을 처리한다면, 내부망의 서버 앞단에서 요청을 처리하는데 이 리버스 프록시의 장점은 보안에 강점이 있기 때문이다. was 웹어플리케이션 서버는 대부분 db서버와 연결되어있다. was 가 최전방에 있으면 보안에 취약해진다. 

위 그림처럼 바로 디비랑 연결이 되어있어서 보안이 매우 취약 한데 여기서 웹서버 인 nginx 를 두면 웹서버가 웹어플리케이션 서버랑 통신해서 결과를 클라이언트에게 제공하면 보안에 취약점을 잡을 수있다.! 서론이 너무 길었다 이제 셋팅을 해보도록 하겟다.

 

터미널을 킨 후 ssh로 개발 서버로 진입

비번을입력 후

NGINX.CONF 파일 찾아내는 명령어 -> sudo find / -name nginx.conf

터미널창에 입력해준다.( nginx 를 이미 개발서버에 설치 한 과정에서 설명)

찾은 후에 

  • 보통 /etc/nginx/* 아래에 설정파일이 위치해있고, 로그파일은 /var/log/nginx/* 에 위치해있다.

ls 로 항목들을 보면 

Nginx.conf(접속자 수, 동작 프로세스 수 등 퍼포먼스에 관한 설정들)

Sites-enabled( 활성화된 사이트들의 설정 파일들이 위치한다. 존재 하지 않은 경우 직접 디렉토리 생성 가능)

등등 이 있다 우선 우리는 

Sites-available ( 설정을 저장하는 곳이다. 여기에 설정을 만든 것은 실제로 nginx에 반영되지는 않습니다. 반영하기 위해서는 sites-enabled에 설정파일을 복사 또는 심볼릭링크를 걸어준다.)

 

nginx의 폴더 안을 들여다 보면 sites-available과 sites-enabled라는 폴더가 존재한다. 이 폴더들은 한 웹서버에서 여러가지 웹 서비스를 다른 도메인으로 운영 할 때를 대비해서 있는 폴더라고 이해하면 된다.

sites-available에는 각 도메인의 고유 설정 파일을 저장 해 두고, sites-enabled에 심볼릭 링크를 작성 혹은 삭제 함으로서 손쉽게 웹 서비스를 실행 혹은 중단 시키는 것이 가능하다.

 

등등으로 이해하고 vim 을 사용하여 

Nginx 가상서버 파일 작성

nginx.conf vim으로 수정하기(메인 설정 파일)

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}

http {
  include    mime.types;

  server {
        listen  8000;

        server_name tmng.tablenjoy.com;

        location @rewrites {
                rewrite ^(.+)$ /index.html last;
        }

        index index.html;

      #charset koi8-r;
      #access_log  logs/tadmin_html/host.access.log  main;
      #root /home/dev/hitable2020-frontend-manager/dist;

      location / {
        root    /home/dev/hitable2020-frontend-manager/dist;
        index   index.html;
        try_files $uri $uri/ @rewrites;
       }
    }

    server {
                listen 8003;
                listen [::]:8003;
                server_name tapi.tablenjoy.com;

                ssl on;
                ssl_certificate /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.crt;
                ssl_certificate_key /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.key;
                ssl_prefer_server_ciphers on;
                error_page 497 https://$host:8003$request_uri;
                location / {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Credentials' 'true';
                        add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
                        add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

                        if ($request_method = 'OPTIONS') {
                                add_header 'Content-Type' 'text/plain charset=UTF-8';
                                add_header 'Content-Length' 0;
                                return 204;
                        }
                        proxy_redirect off;
                        proxy_set_header Host $http_host;
                        proxy_set_header Connction "";
                        proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
                        proxy_pass http://127.0.0.1:8080;
                }

        }

    server {
        listen  8002;

        server_name tmng.tablenjoy.com;
        ssl on;
        ssl_certificate /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.crt;
        ssl_certificate_key /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.key;
        ssl_prefer_server_ciphers on;

        location @rewrites {
                rewrite ^(.+)$ /index.html last;
        }

        index index.html;

      #charset koi8-r;
      #access_log  logs/tadmin_html/host.access.log  main;

      location / {
        root    /home/dev/hitable2020-frontend-brand-admin/dist;
        index   index.html;
        try_files $uri $uri/ @rewrites;
       }
    }

    server {
        listen  8005;

        server_name tmobile.hi-table.com;

        location @rewrites {
                rewrite ^(.+)$ /index.html last;
        }

        index index.html;

      #charset koi8-r;
      #access_log  logs/tmobile_html/host.access.log  main;

      location / {
        root    /home/dev/hitable2020-frontend-store-manager/dist;
        index   index.html;
        try_files $uri $uri/ @rewrites;
       }
    }

        server {
                listen 7400;
                listen [::]:7400;
                server_name tapi.tablenjoy.com;

                ssl on;
                ssl_certificate /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.crt;
                ssl_certificate_key /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.key;
                ssl_prefer_server_ciphers on;
                error_page 497 https://$host:7400$request_uri;
                location / {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Credentials' 'true';
                        add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
                        add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

                        if ($request_method = 'OPTIONS') {
                                add_header 'Content-Type' 'text/plain charset=UTF-8';
                                add_header 'Content-Length' 0;
                                return 204;
                        }
                        proxy_redirect off;
                        proxy_set_header Host $http_host;
                        proxy_set_header Connction "";
                        proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
                        proxy_pass http://127.0.0.1:3000;
                }

        }

        server {
                listen 8001 default_server;
                listen [::]:8001 default_server;
                server_name _;



                ssl on;
                ssl_certificate /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.crt;
                ssl_certificate_key /home/dev/인증서/star_tablenjoy_com/star_tablenjoy_com.key;
                ssl_prefer_server_ciphers on;
                location / {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Credentials' 'true';
                        add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
                        add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

                        if ($request_method = 'OPTIONS') {
                                add_header 'Content-Type' 'text/plain charset=UTF-8';
                                add_header 'Content-Length' 0;
                                return 204;
                        }

                        proxy_redirect off;
                        proxy_set_header Host $http_host;
                        proxy_set_header X-real-ip $remote_addr;
                        proxy_set_header Connction "";
                        proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
                        proxy_pass http://127.0.0.1:3000;
                }
        }

}

                                                                                                                                                                                                                                                                                                                                                          113,0-1       23%

listen 은 port 설정 하는 부분이다. 기본 포트는 80

root는 document root 설정 하는 부분 디폴드 는 html 디렉토리.

proxy_pass 설정은 nginx 뒷 단에 was 가 존재하는 경우이며, 확장자 기반으로 요청 처리를 분리하는 것 location /  이렇게 되어있으면 127.0.0.1:3000 으로 넘기는것이다.

server_name 도메인이름 처럼 바꿔주게 되는데 방문자가 어떤 주소로 들어오냐에따라 해당 도메인 이름을 가진 server{...} 블록이 처리한다.

 

- http 블록

http 블록은 server, location 블록을 포함한다. http 블록을 여러개 생성하여 관리할 수 있지만, 권장사항은 아니다.(권장사항은 http 블록을 하나만 생성하는 것) (oop 상위 클래스 개념으로 생각하면 쉽다.)

 

-서버 블록

- 하나의 웹사이트를 선언하는 데 사용된다. 가상 호스팅(Virtual Host)의 개념이다.

 

- location 블록

서버의 하위단 블록으로 특정 경로, 즉 특정 url을 처리하는 단위이다.

예를 들어  개발 소개 블로그 (www.dev.com) 에서

www.dev.com/nginx   (nginx를 소개하는 카테고리) 

www.dev_moster.com/apache (apache를 소개하는 카테고리)

로 나누고 싶을때 사용할 수 있다.

 

- events 블록

http, server, location 블록과 엮이지 않고 독립적은 블록이다.

주로, 네트워크 동작 방법을 설정한다. 

예를 들어 

events {

worker_connections 100;

}

이렇게 worker_connections 을 100으로 설정하면,

이 웹서버에 한번에 최대 100명이 동시에 접근을 할 수 있다는 것을 의미한다. 

 

ssl 부분은 도메인 인증서, 체인 인증서, 루트 인증서를 발급 완료받은걸 서버에 업로드한다. 그 경로를 적어주면 연결된다.!

ssl_certificate /root/ssl/nginx-ssl.crt;
ssl_certificate_key /root/ssl/nginx-ssl.key;

위에처럼 위치를 지정해주면 된다.

추가적으로 http 로 접속 시 https 로 리다이렉트 하는 부분은 아래와 같이 코드를 추가하면된다.

    return 301 https://$host$request_uri;

아래 리스타트 명령어로 재실행 시킨다.

sudo systemctl restart nginx

site available 에서 설정파일 추가

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        # pass PHP scripts to FastCGI server
        #
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}

server {
    listen 8005;
    listen [::]:8005;

    root /home/dev/hitable2020-frontend-manager/dist;
    index index.html index.htm;
    location / {
       try_files $uri $uri/ /index.html;
    }
}

server {
        listen 8001 ssl;

        root /var/www/html;
        index index.html index.htm;
        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~/.well-known {
                allow all;
        }
}

여기서 심볼릭 링크를 걸어줘야한다 site-adailable/ 에서 만든 사이트를 site-enabled/에 추가해야 활성화가 된다.

site-adailable/ 에 추가한 사이트를 site-enabled/에 심볼릭 링크하여 사이트를 활성화 할 수 있다.

심볼릭 링크랑 윈도위의 바로가기이다.

 

site-enabled 추가

#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        # pass PHP scripts to FastCGI server
        #
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}

server {
    listen 8005;
    listen [::]:8005;

    root /home/dev/hitable2020-frontend-manager/dist;
    index index.html index.htm;
    location / {
       try_files $uri $uri/ /index.html;
    }
}

server {
        listen 8001 ssl;

        root /var/www/html;
        index index.html index.htm;
        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~/.well-known {
                allow all;
        }
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#

conf.d 폴더

  • nginx.conf에서 include로 불러올 수 있는 conf 파일 저장 폴더.

 

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

좀더 디테일하게 하고 싶으면 검색을 하면서 추가적으로 셋팅을 더넣으면 될것같다 내부아이피로 포트를 타면 페이지가 잘뜨는것을 확인할수있다~ 내부아이피타임으로 설정은 추가적으로 들어가면 내부에서 와이파이가 연결되어있을경우에만 회사에서 사용가능한 개발서버가 열린다~

 

101 번서버에 추가적으로 nginx 가 잘연결됐다.

'DevOps > nginx' 카테고리의 다른 글

두개의 서버 설정 - Local 윈도우 기준  (0) 2024.08.27

오늘 회사에서 관리자 쪽에서 특정 매장을 운영상태를 끄면 사이트에서 계속 노출이 된다는 요청사항이 들어와서 소스코드를 분석했다.

기존 코드에서 fetch 에서 백엔드 api 호출 후 데이터를 받는 부분이 있엇다. 새로고침에서는 mounted 에서 제대로 소스코드가 작동을 했지만 뒤로가기 버튼을 통한 매장접속시 백엔드 api 가 작동을 안함을 확인 할 수 있었다. 

 

fetch는 비동기 데이터 호출을 위한 Hook 이며 공식문서에서 간략한 설명으로는

  • fetch hook (Nuxt 2.12+)은 모든 컴포넌트에서 사용할 수 있고, (client-side 렌더링 중) 렌더링이 진행중인 상태와 에러에 대한 참조(shortcuts)를 제공합니다.

페치는 컴포넌트 인스턴스가 생성된 후 server-side 렌더링 동안 또는 client-side 에서 네비게이션 되는 동안 호출되는 hook으로, 비동기 데이터 호출이 완료되었을때 프로미스를 반환한다. 페치는 불필요한 리렌더링을 방지 하기위하여 캐시를 저장하여 데이터를 보존한다. 

mounted 라이프 사이클 쪽에서 페치에서 백엔드 api 쪽에서 전달해준값을 재렌더링으로 사용하지 못하고있엇다.

서버사이드쪽에서 로그가 찍히므로 테스트 진행시에는 로컬서버에서 콘솔로 찍히는값으로 확인이 필요하다.

위와같이 쓰면 터미널 쪽에서는 아래와같이 데이터가 찍힌다.

사이트네임을 넣어두지 않아서 널값이다. 정상적으로 작동을하는것을 확인하였고. 뒤로가기 했을시에는 로그가 안찍히는것을 확인하였다.

mounted 에서만 호출을 하면 재렌더링 되는 부분에서 함수동작을 못한다. 이럴경우 나는 updated 에서 감지하기로 하였다.

 

updated 부분에서 스토어에서 저장한값을 체크한 후 매장이 정상적인 플래그값을 가지고 있는지 재차 확인하는 소스코드를 작성하였다.

checkIsStoreUsed(storeInfo: any): void {

    // siteStore.updateExcludedChannels(false);
    let excludedChannel = false;
    let excludedChannelStr = '';

    for (const i in storeInfo?.excludedChannels) {
      if (String(storeInfo?.excludedChannels[i]) === 'WWW') {
        excludedChannelStr = 'www';
      }
    }
    siteStore.setSoreInfo(this.storeInfo);
    sessionStorage.setItem('storeInfo', JSON.stringify(this.storeInfo));
    if (storeInfo?.enabled === false || excludedChannelStr) {
      excludedChannel = true;
    } else if(storeInfo?.enabled === true && excludedChannelStr === ''){
      excludedChannel = false;
    }

    this.checkIsStoreUsedMessage(excludedChannel);


  }

  checkIsStoreUsedMessage(excludedChannel: boolean): void{
    if (excludedChannel) {
      // this.excludedChannels = true;
      siteStore.updateExcludedChannels(true);
      alert('잘못된 접근입니다.');
      if (!BizModeDefines.saas) {
        this.$router.replace('/intro');
      }else{
        this.$router.replace('/');
      }

    }
  }
//
  // 매장 정보
  //
  private _storeInfo: StoreInfoResponseDto | null = null;

  get soreInfo(): StoreInfoResponseDto | null {
    return this._storeInfo;
  }

  @Mutation
  public setSoreInfo(soreInfo: StoreInfoResponseDto | null): void {
    this._storeInfo = soreInfo;
  }

  @Action
  public updateSoreInfo(soreInfo: StoreInfoResponseDto | null): void {
    this.context.commit('setSoreInfo', soreInfo);
  }

  //
  // 매장 채널 리스트
  //
  private _excludedChannels: boolean = false;

  get excludedChannels(): boolean {
    return this._excludedChannels;
  }

  @Mutation
  public setExcludedChannels(excludedChannels: boolean): void {
    this._excludedChannels = excludedChannels;
  }

  @Action
  public updateExcludedChannels(excludedChannels: boolean): void {
    this.context.commit('setExcludedChannels', excludedChannels);
  }

일단 스토어쪽에 매장 정보를 저장할 수 있는 공간을 만들고 다른페이지에서도 매장을 감지하는 부분이 필요해서 추가적으로 작성해주었다.

 

this.$router.replace 를 사용한이유는 push 를 이용하면 히스토리가 남으면서 추가적으로 다른 이슈상황들이 생기는 부분을 미리 방지하는 차원에서 this.$router.replace 를 사용하면 페이지 이동한다고 보기에는 현재 페이지를 바꿔준다는 개념으로 사용하여 다른 추가적인 에러가 날 수 있는 부분을 방지할수있다.

 

 

 

위와 같은 얼럿 표시 이 후 intro 페이지로 이동한후

 

정상적으로 페이지 이동도 했다.!

다시 뒤로가기 버튼으로 백해도 스토어쪽으로 다시 재진입을 하지못한다!

이걸로 페치를 이용하여 정보를 스토에 담아두고 mounted에서 호출 하는게 아니라 updated에서 재렌더링을 감지하여 코드를 넣어주면 특정 코드를 다시 실행 시킬 수 있게 되었다.

 

이걸로 채널 제외 매장을 화면에서 막을 수 있게 되었다.! 코인의 일지 끄읏~

nuxt 를 사용하여 개발이아닌 운영서버에 배포시 가끔 이런에러가 목격이 된다..

Product 환경에 배포하려고 할 때 발생되는 현상으로,

번들링에서 생기는 오류로써 이를 해결하기 위해서는

gulp 같은 방식을 사용해야 하는데 따로 설치를 하지 않고도 처리를 진행할 수 있는 방법이

nuxt에서는 가능하다.

 

nuxt.config.js 에 들어가서

요부분을 추가해주고 다시 배포하면 해당에러는 해결이 되었다~

 

정상 동작을 확인하였다~

구글에서 검색할경우 제대로 젠킨스까지 연결해서 올려주는 글들이 없어서 내가 노가다로 얻어낸 경험을 공유하고자한다.

jenkins 정적 웹사이트 자동화 배포 글입니당!

글이 이해안갈경우는 댓글 부탁드려용~ㅋㄷㅋㄷ

 

정적 웹사이트 (클라이언트로만 구성되어있는 사이트) -> : Vue로만 만들어진 사이트 인 경우

스토리지 계정이 필요하다(스토리지 어카운트) 앞서서 작성한 게시물참고 

https://asdf1326.tistory.com/9

 

스토리지 계정이 있다는 과정하에 글을 작성하겠다.

젠킨스에 배포하기전에 애저에서 미리 세팅을 진행해주어야한다.

 

스토리지 계정 진입 후 기능 클릭 정적 웹사이트 클릭

 

인덱스 html 과 오류 문서 경로를 기입

화면에서 나와서

 

컨테이너 웹 클릭

공식사이트에서 확인 하고싶은경우 아래 링크로 들어가세요

https://learn.microsoft.com/ko-kr/azure/storage/blobs/storage-blob-static-website-how-to?tabs=azure-portal

 

위내용처럼 업로드

에러 html 은 생각해보니깐 프론트 소스에서 이미 따로 처리하고있음 index.html 로 다 처리할거기떄문에 에러페이지는 따로 업로드 x

 

스토리지 계정 재클릭후 생성된 url 학인후 들어가서 제대로 동작하는지 확인 한다.

vue 정적 파일 업로드에 필요한 젠킨스 파일생성

 

def DOCKER_IMAGE_NAME
def DOCKER_IMAGE_VERSION
def PROD_BUILD = false
pipeline {
    agent {
        node {
            label 'master'
        }
    }

    parameters {
        gitParameter branch: '', branchFilter: '.*', defaultValue: 'origin/master', description: '', listSize: '0', name: 'TAG', quickFilterEnabled: false, selectedValue: 'DEFAULT', sortMode: 'DESCENDING_SMART', tagFilter: '*', type: 'PT_BRANCH_TAG'
        booleanParam defaultValue: false, description: '', name: 'RELEASE'
    }

    environment {
        GIT_URL = "깃url"
        CREDENTIAL_ID = "깃아이디"
        ARTIFACTS = "dist/**"
    }

    options {
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: "30", artifactNumToKeepStr: "30"))
        timeout(time: 120, unit: 'MINUTES')
    }

    stages {
        stage('Install dependencies') {
            steps {
                sh "npm install"
            }
        }

        stage('Build application (test mode)') {
            steps {
                sh "npm run build:dev"
            }
        }

        stage('Deploy application (→ dev)') {
            steps {
                sh "sshpass -p 개발서버비번 scp -r dist 개발서버아이디:hitable2020-frontend-manager"
            }
        }

        stage('Build application (production)') {
            when {
                expression { params.RELEASE == true }
            }
            steps {
                sh "npm run build:production"
            }
        }

        stage('Deploy application (→ azure)') {
            when {
                expression { params.RELEASE == true }
            }
            steps {
                sh "az storage blob upload-batch -s ./dist/ -d '\$web' --account-name 스토리지이름 --account-key 어카운트키 공간"
            }
        }
    }
}

위에 어카운트 키 부분은 애저사이트 진입 후 액세스 키 누르면 어카운트 키 나옴 예시)

 

 

vue 폴더 구조

젠킨스파일 추가완료

 

젠킨스 씨도 추가를 해줘야하지않겟는가?!~

젠킨스 페이지 들어가서 새로운 아이템 클릭

프리스타일 프로젝트로 나는 진행하였다. 다른곳 참고해서 다른 방식대로 해도 무관

클릭 후 다음페이지에서 나는 대충 저런식으로 셋팅

셋팅 변경완료후 저장

각자 개발 셋팅이 다 다르기때문에 우리 회사는 개발서버는 애저를 사용하지않고 vm 쓰고있음 그거에 따른 설정도 바꿔줘야함

 

터미널 킨 후 개발서버 접속 

ssh 접속 ( ssh ID@IP)

젠킨스에서 빌드 배포를 위한 유닉스 권한설정을 변경시켜준다(위에 젠킨스 설정파일에서 잡고있는 루트로 진입)

빨간 부분이 허가권 파란 부분이 소유권 상위디렉토리부터 root 이기떄문에 배포 구문이 막힌다 상위 디렉토리에 소유권을 변경해준후 하위디렉토리에 소유권을 dev로변경, 허가권에 775로 쓰기까지 추가해준다.

 

상 하위 디렉터리에 dev로 소유자 변경: sudo chown -R dev:dev hitable2020-frontend-brand-admin/

디스트 파일에 권한 (쓰기추가)변경 sudo chmod 775 dist/

 

위에 작업까지 완료 후 젠킨스로 진입

젠킨스 체크안하고 개발서버 배포 완료 화면

이로써 배포완료~ 아래 캡처부분이 짤리기했는데 dev까지만 초록창이 네칸이 다 채워져있으면 개발서버는 배포완료이다.

 

'DevOps > 젠킨스' 카테고리의 다른 글

젠킨스 배포시 npm install 무한루프 에러  (0) 2023.01.31

오늘은 리액트 를 통해 디비에 값을 뿌리는 작업을 진행 하겟당!

 

전에 작업한 링크

https://asdf1326.tistory.com/7

 

리액트 express 를 사용하여 MySQL 연동하기

오늘은 리액트 node js express 를 사용하여 MySQL 을 연동할것이다. 근데 오늘 몸상태가 최악..ㅠ 1. 프로젝트 생성 나는 vscode와 웹스톰을 번갈아가면서 쓴다 ㅎ 이번에는 vscode 로 진행을 하였다.~ 터

asdf1326.tistory.com

 

일단 필요한 구조를 설명하자면 

클라이언트 폴더, 서버 폴더 분리해서 직접 생성 한 디비를 연결작업을 진행해보겠다~!

클라이언트 생성명령어 npx create-react-app 폴더명칭 --template typescript

서버측은 위에 링크 참고

위에 폴더를 차례대로 생성 후 일단 디비를 생성

 

스키마 하나 새로 생성 후 use nodejs 로 데이터베이스 선택

위에처럼 생성

데이터 추가 후 명령어로 테이블 확인

 

index.js 

const express = require("express");
const app = express();
const mysql = require("mysql");
const PORT = process.env.port || 3000;

const db = mysql.createPool({
  host: "127.0.0.1",
  user: "root",
  password: "0000",
  database: "nodejs",
});

 app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
  res.header("Access-Control-Allow-Headers", "x-access-token, Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.get("/list", (req, res) => {
  const sqlQuery = "SELECT *FROM BOARD;";
  db.query(sqlQuery, (err, result) => {
    res.send(result);
  });
});

app.listen(PORT, () => {
  console.log(`running on port ${PORT}`);
});

여기서 주의할점이 가끔 cors 에러가 뜨곤 한다 미리 사전의 방지를 하는것이 좋다!

header에 추가적인 조치를 취해준다. 응답 헤더부분에 추가적으로 넣으면 된다. 내가 추가한 값이 제대로 들어가있다~

원래는 다른 코드를 써서 조작을해야 안전하게 하나의 도메인만 받는게 좋다 * 로 설정해둔 오리진 부분은 사실 많이 쓰지 않는 방식이다.!

인터넷 검색으로 치면 많은 설명들이 나온다 꼭 개발이 아닌과정에서는 확인 후 사용해주자~

이제 서버측은 셋팅이 끝낫당.!

클라이언트쪽으로 넘어가서 만들어보자~

클라이언트 소스확인 가능한 깃허브 주소 : https://github.com/asdf132645/client

 

GitHub - asdf132645/client

Contribute to asdf132645/client development by creating an account on GitHub.

github.com

import { Component } from "react";
import Axios from "axios";
import Table from "react-bootstrap/Table";
import Button from "react-bootstrap/Button"; 

const Board = ({
  id,
  title,
  registerId,
  registerDate,
}: {
  id: number;
  title: string;
  registerId: string;
  registerDate: string;
}) => {
  return (
    <tr>
      <td>
        <input type="checkbox"></input>
      </td>
      <td>{id}</td>
      <td>{title}</td>
      <td>{registerId}</td>
      <td>{registerDate}</td>
    </tr>
  );
};

/**
 * BoardList class
 */
class BoardList extends Component {
  state = {
    boardList: [],
  };

  getList = () => {
    Axios.get("http://localhost:3000/list", {})
      .then((res) => {
        const { data } = res;
        this.setState({
          boardList: data,
        });
      })
      .catch((e) => {
        console.error(e);
      });
  };

  /**
   */
  componentDidMount() {
    this.getList();
  }

  /**
   * @return {Component} Component
   */
  render() {
    // eslint-disable-next-line
    const { boardList }: { boardList: any } = this.state;

    return (
      <div>
        <Table striped bordered hover>
          <thead>
            <tr>
              <th>선택</th>
              <th>번호</th>
              <th>제목</th>
              <th>작성자</th>
              <th>작성일</th>
            </tr>
          </thead>
          <tbody>
            {
              // eslint-disable-next-line
              boardList.map((v: any) => {
                return (
                  <Board
                    id={v.BOARD_ID}
                    title={v.BOARD_TITLE}
                    registerId={v.REGISTER_ID}
                    registerDate={v.REGISTER_DATE}
                  />
                );
              })
            }
          </tbody>
        </Table>
        <Button variant="info">글쓰기</Button>
        <Button variant="secondary">수정하기</Button>
        <Button variant="danger">삭제하기</Button>
      </div>
    );
  }
}

export default BoardList;

npm install axios 로 모듈 설치해주고

우리는 서버 포트는 3000이다. 명확하게 클라이언트 포트도 나눠주자!

요로코롬 start 할때 포트 8000 으로 이어주자~ 그래야 겹치지 않고 서로다른 포트에서 연결이 잘되니깐!

이제 마지막으로 화면을 확인하고 네트워크를 봐주자~

잘나온다 ! 네트워크도 확인 해보자!

 

응답값이 아주이쁘게 들어가있다.!! 뷰만 할때는 못느낀건데.. 컴포넌트 반복문 쓸때 좀 낮설다.. map 함수를 써서 반환 시키는게 참 낮설다... 뷰는 v-for 써주고 key 써주면 아주 이쁘게 잘들가는뎅.. 참 낮설어...! 암튼이게 문제가 아니고 다음에는 테이블내용 추가 수정등을 해볼것이다~ 그리고 리덕스도 차근차근 적용하고~ 라우터도 해볼것이다~ 이것으로 코인의 백엔드 조작 탐방 일기 끄읏~~~

다음 일기에서 계속~!~

회사에서 카카오페이를 사용자사이트에 연결해달라는 요청이 들어왔다~

카카오페이를 연동하는 방법에 대해서 간략하게 일기를 쓸예정이다!

 

카카오페이 개발 참고페이지

- 카카오페이 개발API : https://developers.kakao.com/docs/latest/ko/kakaopay/common
- 카카오페이 데모 : https://developers.kakao.com/demo/pay/index
- 응답코드 : https://developers.kakao.com/docs/latest/ko/reference/rest-api-reference

 

필요한 디렉터리 파일

pages/buy/kakaopay/failPage.vue // 카카오페이 실패일 경우

pages/buy/kakaopay/index.vue // 승인요청에 대한 처리 로직, 클래스 등 필요한 로직 처리

pages/buy/index.vue // 결제페이지

 

api 로직 디렉터리

common/api/service/payment/kakaopay/KakaoPayApi.ts // 카카오페이 api 로직

common/api/service/payment/kakaopay/dto/kakaoPayReadyDto.ts // 카카오페이 준비 dto 

common/api/service/payment/kakaopay/dto/kakaoPayCompleteDto.ts // 카카오페이 승인요청완료 dto

 

부수적으로 필요한 ts파일은 생략하겟습니당.!

 

먼저 결제 페이지인 pages/buy/index.ts 에서

주문대기 생성을 하여 order 쪽 백엔드쪽에서 요청을 실행하고 나면 주문번호가 생성이 된다.

그후에 카카오페이를 선택 했을 시에 일어나는 로직을 만들도록 하자!

 

buy/index.ts 에서 결제방법은 컴포넌트로 새로 만들어서 분기처리해준다.

components/views/buy/PaymentMethod.vue 에 결제방법 UI를 생성 시켜준다.

카카오페이에서 제공하는 이미지를 사용하여 해당 UI를 만들어준다.

회사 기획서에 맞게 페이에이전트타입으로 분리하여 그려준다.

대략 이런식으로 만든 후

 

pages/buy/index.vue 로 이동한다.

대기주문을 생성이 완료된후 로직을 탈수있게 코드를 짜준다.

 

proceedPayment 함수를 호출하면 아래와 같은 코드를 삽입

proceedPayment 안에 중간 내용 페이먼트 에이전트 타입에 따른 실행 코드 생성

setorderno를 만든이유는 카카오페이는 자신들의 페이지로 이동을 하고있다.

그러면 페이지간 이동이 일어나서 전에 내가 store 에서 저장했던 내용들이 날라간다.

그래서 그앞단인 buy 페이지에서 미리 세션스토리지에 값을 가지고 있기로 해서 위와 같이 만들었다.

 

카카오페이에서는 리다이렉트 url을 3개를 받고있는 이유이다.  아래 카카오페이에서 보여주는 예시를 보면

요런식으로 응답값을 준다. 위에 값은 우리회사 백엔드 개발자가 준다고 하니 나는 거기에 맞는 처리를 진행하였다.

하이브리드 웹이라서 웹뷰로 모바일까지 진행하고있다보니 안드로이드나 ios에 대한 처리는 따로 하지않았다!

회사 규격에 맞게 셋팅에 맞게 하면 된다고 보여진다.

 

kakaoPayReadyDto.ts



export interface kakaoPayReadyResponseDto{
  tid: string;
  // eslint-disable-next-line camelcase
  next_redirect_pc_url: string;
  // eslint-disable-next-line camelcase
  next_redirect_app_url:string;
  nextRedirectMobileUrl: string;
  createdAt: string;
}

export interface kakaoPayApproveRequestDto{
  tid: string;
  orderId: string;
  pgTocken: string;
  totalAmount: number;
}

export interface  kakaoPayReadyRequestDto {
  orderNumber: string;
  goodsName: string | undefined;
  buyerName: string;
  buyerPhone: string;
  paymentAmount: string;
  paymentMethod: string;
  paymentAgentType: string;
  approvalUrl: string;
  cancelUrl: string | null;
  failUrl:string | null;
  quantity: number;
  buyerEmail: string;
  pointAmount: string;
  couponNumber: string | null ;
}

export class kakaoPayReadyUtils {
  static createDto(
    orderNumber: string,
    goodsName: string,
    buyerName: string,
    buyerPhone: string,
    paymentAmount: string,
    paymentMethod: string,
    paymentAgentType: string,
    approvalUrl: string,
    cancelUrl: string | null,
    failUrl: string | null,
    quantity: number,
    buyerEmail: string,
    pointAmount: number,
    couponNumber: string | null ,
                   ) : kakaoPayReadyRequestDto{
      return {
        orderNumber,
        goodsName,
        buyerName,
        buyerPhone,
        paymentAmount,
        paymentMethod,
        paymentAgentType,
        approvalUrl,
        cancelUrl,
        failUrl,
        quantity,
        buyerEmail,
        pointAmount: pointAmount > 0 ? String(pointAmount) : '',
        couponNumber,
      };
  }

  static errMessage(errMsg: string){
    let newStr = '';
    switch (errMsg) {
      case '400':
        newStr  = '요청한 파라미터값이 잘못되었습니다.';
        break;
      case '701':
        newStr  = '결제 인증이 완료되지 않았습니다.';
        break;
      case '702':
        newStr = '이미 결제 완료된 결제건 입니다.';
        break;
      case '703':
        newStr = '결제 포인트 금액이 잘못됐습니다.';
        break;
      case '704':
        newStr = '결제 금액이 잘못됐습니다.';
        break;
      case '705':
        newStr = '지원하지 않는 결제 수단입니다.';
        break;
      case '706':
        newStr = '주문번호와 다른 값으로 API 출했습니다.';
        break;
      case '707':
        newStr = '결제 준비 API에서 요청한 partner_user_id와 다른 값으로 결제승인 API 호출 했습니다.';
        break;
      case '708':
        newStr = '잘못된 pg_token로 결제승인 API를 호출했습니다.';
        break;
      case '710':
        newStr = '결제 취소 API 호출 시 취소 요청 금액을 취소 가능액보다 큰 금액으로 요청했습니다.';
        break;
      case '721':
        newStr = 'TID가 존재하지 않습니다.';
        break;
      case '722':
        newStr = '금액 정보가 잘못된 경우';
        break;
      case '723':
        newStr = '결제 만료 시간이 지났습니디.';
        break;
      case '724':
        newStr = '단건 결제 금액이 잘못되었습니다.';
        break;
      case '725':
        newStr = '총 결제 금액이 잘못되었습니다.';
        break;
      case '726':
        newStr = '주문 정보가 잘못되었습니다.';
        break;
      case '730':
        newStr = '가맹점 앱 정보가 잘못되었습니다.';
        break;
      case '731':
        newStr = 'CID 가 잘못됐습니다.';
        break;
      case '732':
        newStr = 'GID 가 잘못됐습니다.';
        break;
      case '733':
        newStr = 'CID_SECRET이 잘못됐습니다.';
        break;
      case '750':
        newStr = 'SID가 존재하지 않습니다.';
        break;
      case '751':
        newStr = '비활성화된 SID로 정기결제 API를 호출했습니다.';
        break;
      case '752':
        newStr = 'SID가 월 최대 사용 회수를 초과했습니다.';
        break;
      case '753':
        newStr = '정기 결제 API 호출 시 partner_user_id가 SID를 발급받았던 최초 결제 준비 API에서 요청한 값과 다릅니다.';
        break;
      case '761':
        newStr = '입력한 전화번호가 카카오톡에 가입하지 않았습니다.';
        break;
      case '780':
        newStr = '결제 승인 API 호출 실패';
        break;
      case '781':
        newStr = '결제 취소 API 호출 실패';
        break;
      case '782':
        newStr = '정기 결제 API 호출 실패';
        break;
      case '783':
        newStr = '승인 요청을 할 수 없는 상태에서 결제 승인 API를 호출';
        break;
      case '784':
        newStr = '취소 요청을 할 수 없는 상태에서 결제 취소 API를 호출';
        break;
      case '785':
        newStr = '결제와 취소를 중복으로 요청';
        break;
      case '797':
        newStr = '1회 결제 한도 금액을 초과했습니다.';
        break;
      case '798':
        newStr = '허용되지 않는 IP를 사용하셧습니다.';
        break;
      case '799':
        newStr = '등록된 웹사이트 도메인의 설정과 요청 도메인이 다릅니다.';
        break;
    }
    return newStr;
  }
}

 

kakaoPayApi.ts

 

카카오페이 요청 dto를 만들어주고 try 문에 다가

pc 일떄와 모바일때를 분리하여 백엔드에서 던져주는 url로 카카오페이로 페이지 이동을 시킨다~

 

사용자 페이지로 이동하여 현재까지 잘되었는지 테스트를 해본다!

아래는 카카오페이지 이동 후 화면이다.

위에 창도 잘뜨는건 확인 했으니 네트워크를 열어서 요청값들이 잘넘어갔는지도 확인을해준다.

 

내가 셋팅한것처럼 완료,실패,사용자취소 url 도 잘 셋팅이 되어서 넘어가있다!

사용자 취소 부분도 잘되는지 확인 해보자

우리 회사 기획서 상 결제페이지에 남아있지않도록하고 (중복결제에대한 문제점을 생각하셔서 그렇게 한것같다.) 매장이나 상품페이지로

이동하도록 기획을 하셧다.

 

위에 얼럿은 미리 내가 코딩을 해서 페이지 이동시 시스템 얼럿창이 뜨도록 설계를 하였다.

자 이제 승인요청 완료 부분이다!

 

kakaoPayCompleteDto.ts

export interface KakaoCompleteRequestDto {
  tid: string | null;
  orderNumber: string;
  pgToken: string;
  payType: string;
  paymentMethod: string;
}

export class KakaoPayCompleteRequestDtoUtils {
  static createDto(tidNo: string | null, orderNum: string, pgTokenStr: string, payTypeStr: string): KakaoCompleteRequestDto {
    return {
      orderNumber: orderNum,
      tid: tidNo,
      pgToken: pgTokenStr,
      payType: payTypeStr,
      paymentMethod: 'KAKAOPAY'
    };
  }
}

export interface KakaoCompleteResponseDto {
  data: {
    aid: string;
    tid: string;
    cid: string;
    // eslint-disable-next-line camelcase
    partner_order_id: string;
    // eslint-disable-next-line camelcase
    partner_user_id: string;
    // eslint-disable-next-line camelcase
    payment_method_type: string;
    amount:{
      total: number;
      // eslint-disable-next-line camelcase
      tax_free: number;
      vat: number;
      point: number;
      discount: number;
      // eslint-disable-next-line camelcase
      green_deposit: number;
    };
    cardInfo:{
      // eslint-disable-next-line camelcase
      purchase_corp: string;
      // eslint-disable-next-line camelcase
      purchase_corp_code: string;
      // eslint-disable-next-line camelcase
      issuer_corp: string;
      // eslint-disable-next-line camelcase
      issuer_corp_code: string;
      // eslint-disable-next-line camelcase
      kakaopay_purchase_corp: string;
      // eslint-disable-next-line camelcase
      kakaopay_purchase_corp_code: string;
      // eslint-disable-next-line camelcase
      kakaopay_issuer_corp: string;
      // eslint-disable-next-line camelcase
      kakaopay_issuer_corp_code: string;
      bin:string;
      // eslint-disable-next-line camelcase
      card_type: string;
      // eslint-disable-next-line camelcase
      install_month: string;
      // eslint-disable-next-line camelcase
      approved_id: string;
      // eslint-disable-next-line camelcase
      card_mid: string;
      // eslint-disable-next-line camelcase
      interest_free_install: string;
      // eslint-disable-next-line camelcase
      card_item_code: string;
    };
  }
}

 

 

2탄에서 계속!

+ Recent posts