현 회사에 이직하기로 마음먹은 건 새로운 도전을 할 수 있을 것 같다는 강한 끌림과 프런트엔드만 접하다가 

간단한 웹 백엔드 서버까지 구축하여 도전을 할 수 있다는 점이 가장 크게 와닿았고 회사의 비전이 정말 괜찮다고 느껴져서 이직 후 6개월가량 밤샘 작업을 하면서 회사의 이바지하였다..

 

기존 전임자가 짜놓고 간 일렉트론으로 만든 코드는.. 정말 형편이 없었고 딜레이부터 잔 버그들 그리고 사용하지 못할 정도로 속도가 느렸고.. 부분 부분 도메인지식도 전체적으로 다 틀렸다.. 전임자가 사용 한 부분은 일렉트론 + mysql + 백그라운드 js를 사용하였다. 정말이지.. 리뉴얼하는데 엄청 진을 뺐다..

 

문제가 되었던 부분을 정리하자면

1. cdn 방식이 아닌 폴더 자체 접근 기존 데이터를 알지 못해서 계속 추가적인.. 중복코드 작성

2. 무분별한 store 사용

3. 이벤트 버스를 사용 함으로 자기가 만들어 놓은 로직이 어디에 있는지 몰라서 추가적으로 중복코드 작성

4. 아키텍처가 없고 규칙이 없는 폴더구조

5. 유지보수가 힘든 명령형 프로그래밍 구조

6. 웹 백엔드가 없어서 백엔드 에러로그 확인이 힘듦

7. 비동기처리에 부재로 시점차이 오류가 남발

8. 라이프사이클을 제대로 활용하지 않아서 시점 차이 발생..

 

전임자가.. SI 출신 12년차 백엔드 개발자라고 전달 받아서 들었다.. 그의 코드를 뜯어보고 나는 경악을 했다.. 신입이 짜놓은 듯한 ... 

 

기존 코드들은 대부분 명령형 + 이벤트버스 남발.. if문에 indexOf.. 등등 es7 등 문법은 찾아보기도 힘들었다..

 

기존 전임자 코드의 아키텍처

사실상 컴포넌트에 tabPages 만 사용 하고 안쓰는 컴포넌트가 태반이였다.

그리고 알 수 없는 main 이라는 폴더는 사용도 안하고 있다..

 

리팩토링한 아키텍처 구조는

 

Api 계층을 담당하는 부분을 common으로 재사용이 가능하게 담아두고 서비스 별로 분리해둔 후 재사용성에 비중을 뒀다..

현 회사의 특이점으로는 폐쇄망으로 내부망쪽에 통신을 해서 값을 올리는 부분이 있는데 그부분에 인수인계가 하나도 없었다.. 순서도를 그려서 대략적인 커스텀마이징을 걸치긴 해야하지만 아래와같이 작업을 진행 하였다.

 

 

순서도로 미리 로직을 그려본 후 코드를 만들어가기 시작했다. 모든 로직을 새로 순서도를 그려서 체계적으로 만들기 시작하였고 새로운 로직으로 버그투성이에다가 돌아가지 않는 전임자 코드를 안쓰고 새로 만들었다.

전임자 코드.... 무분별한 이벤트 버스.. 남발 어디서 뭘하는지 전혀 모르겠다..

되도록 props 를 사용하며 전역 스토어는 지향하였다 스토어에 무분별한 사용도 성능 이슈에 영향을 끼치기 때문에 주의 하면서 쓰도록 하였다.

 

기존 코드 리팩토링 작업을 들어간다.

기존 코드

위에 기존 코드

리팩토링한 코드

if (buff.indexOf('\n') === -1) {
      bufStr += buff.toString()
    } else {
      bufStr += buff.toString()

      self.postMessage({params: JSON.parse(bufStr.toString())})
      bufStr = ''
    }
기존 전임자 코드

ES7에서는 .includes() 메서드가 도입되어 배열에 특정 값이 존재하는지 
확인할 때 더 간결하고 직관적인 방법을 사용할 수 있습니다.
indexOf() 메서드와 비교할 때 가독성 면에서 우수하며
indexOf()는 -1을 반환하는 대신 true 또는 false로 존재 여부를 바로 알 수 있다.

if (!buff.includes('\n')) { // indexOf 대신 includes 사용
  bufStr += buff.toString();
} else {
  bufStr += buff.toString();

  self.postMessage({ params: JSON.parse(bufStr.toString()) });
  bufStr = '';
}

 

 

전임자 코드

async / await 사용 비동기 작업을 효율적으로 관리하여 성능을 크게 향상시킬 수 있다.

기존의 콜백 함수나 Promise 체인보다 훨씬 가독성이 좋고, 비동기 코드 작성을 간소화하여 네트워크 요청, 파일 읽기/쓰기 등의 작업을 최적화할 수 있다.

대략 위에 같이 함수형 프로그래밍으로 변경을 했고

성능쪽 이슈를 해결해보겠다.

 

기존 테이블 리스트 5개 기준 속도 측정 결과 

기존 전임자 일렉트론 코드

 

리팩토링 코드

레디스 캐시 서버와 프론트 측 불필요한 스크립트를 걷어내서 속도 향상을 하였고 불필요한 비동기 함수들을 걷어냈다.

 

서버측에서는 TCP WEBSOCKET  을 사용하여 통신 측 서비스를 하나 만들었고 거기에 로거 서비스를 만들어서 로그를 생성해서 폴더에 생성했다. 아래는 서비스 로거 코드

private lastMessages = {
    log: null,
    error: null,
    warn: null,
    debug: null,
    cbcLis: null,
    ping: null,
    login: null,
  }; // 각 로그 레벨별 마지막 메시지 저장

  constructor() {
    super();
    this.ensureBaseLogDirectoryExists();
  }

  log(message: string) {
    if (this.isDuplicateMessage('log', message)) return;
    super.log(message);
    this.writeLog('log', message);
  }

  error(message: string, trace?: string) {
    const fullMessage = trace ? `${message}\n${trace}` : message;
    if (this.isDuplicateMessage('error', fullMessage)) return;
    super.error(message, trace);
    this.writeLog('error', fullMessage);
  }

  warn(message: string) {
    if (this.isDuplicateMessage('warn', message)) return;
    super.warn(message);
    this.writeLog('warn', message);
  }

  debug(message: string) {
    if (this.isDuplicateMessage('debug', message)) return;
    super.debug(message);
    this.writeLog('debug', message);
  }

  cbcLis(message: string) {
    if (this.isDuplicateMessage('cbcLis', message)) return;
    super.log(message);
    this.writeLog('cbcLis', message);
  }

  ping(message: string) {
    if (this.isDuplicateMessage('ping', message)) return;
    super.log(message);
    this.writeLog('ping', message);
  }

  logic(message: string) {
    if (this.isDuplicateMessage('logic', message)) return;
    super.log(message);
    this.writeLog('logic', message);
  }

  private isDuplicateMessage(level: string, message: string): boolean {
    if (this.lastMessages[level] === message) {
      return true; // 중복 메시지일 경우 true 반환
    }
    this.lastMessages[level] = message; // 마지막 메시지를 갱신
    return false; // 중복이 아니면 false 반환
  }

  private formattedTime(date: Date) {
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');
    const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
    return `[${hours}:${minutes}:${seconds}.${milliseconds}]`;
  }

  private writeLog(level: string, message: string) {
    const now = new Date();
    const dateString = moment(now).format('YYYY-MM-DD'); // 현재 로컬 시간 기준으로 날짜 생성
    const logDir = path.join(this.baseLogDir, level);

    this.ensureDirectoryExists(logDir);

    const logFilePath = path.join(logDir, `${dateString}_${level}.txt`);

    this.ensureFileExists(logFilePath);

    const formattedMessage = `${this.formattedTime(now)} - ${message}`;
    fs.appendFileSync(logFilePath, `${formattedMessage}\n`);
  }

  private ensureBaseLogDirectoryExists() {
    this.ensureDirectoryExists(this.baseLogDir);
  }

  private ensureDirectoryExists(dir: string) {
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
      console.log(`로그 디렉토리가 존재하지 않아서 생성: ${dir}`);
    }
  }

  private ensureFileExists(file: string) {
    if (!fs.existsSync(file)) {
      fs.writeFileSync(file, '');
    }
  }

 

아래는 생성된 로그 폴더

로컬 PC에 맞게 로그도 남기면서 성능 이슈도 해결 했다.

서버 측 작업과 프론트 쪽에서도 많은 작업을 했지만 나중에 추후에 더 깊게 작성 해볼 예정이다! 끝

이직 한 회사에서 Ai 코어 측에서 만들어주는 이미지 폴더안에 

이런식으로 폴더가 생성되어있는곳에서 이미지를 조합해서 화면에 표시해줘야하는 작업이 생겼다.

json으로는 좌표를 표시해서 화면에서 해당 좌표에 사각형으로 표시를 해주는 작업이 추가 되었다.

이제 로직을 구현 해볼것이다.

마운티드에 함수 실행 할 부분을 추가 해준다.

const fetchTilesInfo = async (folderPath: string) => {
  const url = `${apiBaseUrl}/folders?folderPath=${folderPath}`;
  const response = await fetch(url);

  if (!response.ok) {
    tileExist.value = false;
    throw new Error('Network response was not ok');
  } else {
    const fileNames = await response.json();
    const tilesInfo = [];
    fileNameResultArr.value = [];
    for (const fileName of fileNames) {
      if (fileName.endsWith('_files')) {

        const fileNameResult = extractSubStringBeforeFiles(fileName);
        fileNameResultArr.value.push(fileNameResult)
        const {width, height} = await dziWidthHeight(fileNameResult)

        tilesInfo.push({
          Image: {
            xmlns: "http://schemas.microsoft.com/deepzoom/2009",
            Url: `${apiBaseUrl}/folders?folderPath=${folderPath}/${fileName}/`,
            Format: "jpg",
            Overlap: "1",
            TileSize: "1024",
            Size: {
              Width: width,
              Height: height
            }
          }
        });

        canvasCurrentWitdh.value = width;
        canvasCurrentHeight.value = height;
      }
    }
    tileExist.value = true;
    return tilesInfo;
  }
};

일단.. 웹 회사에서 근무할 때 선언형(함수형 프로그래밍)을 많이 써왔지만 사이드 이펙트가 발생 할 여지가 있지만  파일 폴더 측에 접근해서 좀 더 직관적인 명령형으로 어떤식으로 흘러가는지 파악하기 위해서 명령형으로 작성해준다. 

 

먼저 백엔드 측에서 이미지를 CDN 방식으로 만들어둔 Api 를 통해서 응답을 받아온다.

응답 값

제대로 담기는 모습을 확인 후 

viewer.value = OpenSeadragon({
        id: "tiling-viewer_img_list",
        animationTime: 0.4,
        navigatorSizeRatio: 0.25,
        showNavigator: true,
        sequenceMode: true,
        defaultZoomLevel: 1,
        prefixUrl: `${apiBaseUrl}/folders?folderPath=D:/UIMD_Data/Res/uimdFe/images/`,
        tileSources: tilesInfo,
        showReferenceStrip: false,
        gestureSettingsMouse: {clickToZoom: false},
        maxZoomLevel: 15,
        minZoomLevel: 1, // 최소 확대 레벨 설정
        zoomPerClick: 1.2, // 클릭 확대 비율 설정
        zoomPerScroll: 1.2, // 스크롤 확대 비율 설정
        viewportMargins: {top: 0, left: 0, bottom: 0, right: 0}, // 뷰포트 여백 설정
        visibilityRatio: 1.0 // 이미지를 뷰포트에 맞추기 위한 비율 설정
      });

위에처럼 작성 후 담아주고 

// 마그니파이어 설정 - 동그라미 줌기능
      new OpenSeadragon.MouseTracker({
        element: viewer.value.element,
        moveHandler: function (event) {
          const existingMagCanvas = document.getElementById('magCanvas');
          if (existingMagCanvas) {
            viewer.value.element.removeChild(existingMagCanvas);
          }

          if (!isMagnifyingGlass.value) {
            return;
          }

          const {canvas} = viewer.value.drawer;
          const magCanvas = document.createElement('canvas');
          const magCtx = magCanvas.getContext('2d');
          canvasOverlay.value = magCanvas;
          if (magCtx) {
            const magWidth = 200;
            const magHeight = 200;
            const zoomLevel = 5;

            magCanvas.id = 'magCanvas';
            magCanvas.width = magWidth;
            magCanvas.height = magHeight;
            magCanvas.style.position = 'absolute';
            magCanvas.style.left = `${event.position.x - magWidth / 2}px`;
            magCanvas.style.top = `${event.position.y - magHeight / 2}px`;
            magCanvas.style.border = '1px solid';
            magCanvas.style.borderRadius = '50%';
            magCanvas.style.width = `${magWidth}px`;
            magCanvas.style.height = `${magHeight}px`;
            magCanvas.style.zIndex = '0';

            viewer.value.element.appendChild(magCanvas);

            // 줌을 위한 확대된 부분을 정확히 잘라내기 위해 drawImage 메서드 수정
            magCtx.drawImage(
                canvas,
                event.position.x - (magWidth / 2 / zoomLevel),
                event.position.y - (magHeight / 2 / zoomLevel),
                magWidth / zoomLevel,
                magHeight / zoomLevel,
                0,
                0,
                magWidth,
                magHeight
            );

            magCanvas.style.visibility = event.position.y <= 0 || event.position.x <= 0 ? 'hidden' : 'visible';
          }
        },
      });
      
      viewer.value.addHandler('open', function (event: any) {
        // 캔버스 크기를 조정
        canvas.width = event.source.Image.Size.Width;
        canvas.height = event.source.Image.Size.Height;
      });

      viewer.value.addHandler('page', function (event: any) {
        const notCanvasClick = fileNameResultArr.value[event.page] !== 'RBC_Image_0';
        emits('notCanvasClick', notCanvasClick);
        // 페이지가 변경될 때 오버레이를 다시 추가
        if (canvas.parentElement !== viewer.value.container) {
          viewer.value.addOverlay({
            element: canvas,
            location: new OpenSeadragon.Rect(0, 0, 1, 1),
          });
        }
        emits('unChecked');
      });
     viewer.value.addHandler('zoom', () => {
        if (activeRuler.value === 'None') {
          return;
        }
        drawRuler(activeRuler.value);
      });
      
      viewer.value.addHandler('canvas-click', async (event: any) => {
      if (!event.originalEvent.ctrlKey) {
          await removeDiv();
        }
        
         const clickPos = viewer.value.viewport.pointFromPixel(event.position);
          const canvasPos = {
            x: clickPos.x * viewer.value.source.width,
            y: clickPos.y * viewer.value.source.height
          };

          // 함수: 클릭 위치가 아이템 위치와 겹치는지 확인
          const isItemSelected = (item: any) => {
            const width = item.width;
            const height = item.height;
            return (
                canvasPos.x >= Number(item.posX) && canvasPos.x <= (Number(item.posX) + width) &&
                canvasPos.y >= Number(item.posY) && canvasPos.y <= (Number(item.posY) + height)
            );
          };

          // 선택된 아이템의 classNm 저장
          let selectItm = '';
          for (const item of drawPath.value) {
            if (item.classNm !== "Normal" && isItemSelected(item)) {
              selectItm = item.classNm;
              break; // 하나의 아이템만 선택됨
            }
          }
          아래 코드는 생략

줌 기능, OPEN , 페이지넘기기, 캔버스 클릭 시 사각형 생성 등등.. 필요한 부분들을 넣어준다.

 

혈액 RBC에는 클래스 리스트에 맞게 체크를 넣어줘야 하므로 watch 로 현재 클릭 되어있는 체크배열을 받아서 그려주는 로직을 추가 해준다. any 가 많은건.. 혼자서 전체 로직을 짜고 데모 버전으로 ㅠ 3개월만에 완성을 시켜야해서 다급하게 하는라 타입을 지정해주지 못했다 ㅠㅠㅠㅠㅠㅠ 따로 추후에 리팩토링 과정을 통해서 전체 수정할 예정이다..!

 

watch(() => props.classInfoArr, (newData) => {
  newItemClassInfoArr.value = newData;

  if (newData.length === 0) {
    removeDiv();
    removeRbcMarker();
    return;
  }

  // 모든 <ol> 요소를 선택하고, data-class-nm 값을 배열로 수집
  const olElements = document.querySelectorAll('ol.overlayElement');

  // newData 배열에서 존재하는 data-class-nm 값을 수집
  const validClassNmSet = new Set(newData.map((el: any) => el.classNm));

  olElements.forEach(el => {
    const classNm = el.getAttribute('data-class-nm');

    // data-class-nm이 newData에 존재하지 않으면 해당 <ol> 요소를 제거
    if (!validClassNmSet.has(classNm)) {
      console.log('Removing <ol> with data-class-nm:', classNm);
      viewer.value.removeOverlay(el);
    }
  });

  // rbcMarker 함수 호출
  rbcMarker(newData);
}, {deep: true});

대략적인 모든 기능을 구현이 완성 된 후 화면은 아래와 같다

 

혼자서...3일안에 완성시킨.. 오픈시드래곤을 활용한 타일화 ! 좀 고생을 많이 했다 오픈시드래곤 공식 문서를 보면서 일일이 다 고치는라 고생한 ... 정말 힘들었다. 추후에 타입추가와 리팩토링을 걸쳐서 수정해야겠다.

회사 특성상 장비에서 TCP 통신을 통하여 AI 데이터베이스 코어 쪽과 통신을 해서 웹 백 그리고 부분을 무겁지 않은 NestJs 채택하여 구축하였다. 첫 도전이었기에 프런트 개발자로서.. 익숙함과 러닝커브가 많지 않다는 과정하에 nests를 활용하여 프런트와도 양방향 통신이 가능한 웹 소켓 연결이 수월한 NestJs 와 mysql을 사용하였다. 

현 회사에 특징 중 많은 이미지 파일 이동 + 1대의 Main PC에 내부망에 있는 PC 5대의 동시 접속까지는 고려해야 하는 상황이었다..

Nginx 에 프락시를 사용하여 로드밸런스를 해주고 특정 IP를 막는 용도로 사용하고 있는 중 전체 리스트 항목을 가지고 올 때 너무 느리다는 단점이 부각 됐다.(멀티 부어 PC에서 이미지를 옮기는 와중 너무 느리다는 단점이 부각) 이점을 고치기 위하여 첫 웹백엔드  개발 쪽에 심중 있게 살펴보던 와중 Redis 서버가 눈에 들어왔다.


레디스에 장점 중에는 in-memory cache이라는 큰 장점이 있었다. 기존 nests 서버 옆은 SQL 에 하드웨어로 읽고 쓰기를 진행 했다면 Redis는 랜덤 액세스 메모리(RAM)를 사용하여 엄청난 속도를 보여줬다. 레디스 서버에서 미리 가지고 있다가 프론트가 요청을 하면 바로 보여주는 방식이라서 정말 엄청 빨라진 것을 볼 수 있었다.



윈도우 기준 설치 방법은 정말 간단하다.

사이트 접속 -&TG; MIS 설치

Releases · microsoftarchive/redid (github.com)

 

윈도우 기준 설치 방법은 정말 간단하다.

사이트 접속 -> msi 설치

Releases · microsoftarchive/redis (github.com)

설치 후 자동으로 서비스에 등록이 되어 컴퓨터를 재시작해도 자동으로 서버가 실행된다.

Redis의 기본포트는 6379 이다.

위에처럼 떠 있으면 제대로 실행 된거다.

 

너무 느렸던 list 페이지에 도입을 했더니 스냅샷에 엄청난 단축을 보여줬다.

 

기존은 2230ms 유휴상태를 제거해도 그 정도 걸렸다.

엄청난 단축이였다..

 

http 통신으로 다시 재 호출 하지 않고 호출하면서 배열을 조작하는 부분들이 다시 로직을 안 쓰다보니깐 많은 단축을 보여줬다! ㅎㅎ

 

백엔드 코드에서 작업은

인터셉터로 먼저 가로채서 작업을 진행 하기로 했다. 우선 인터셉터로 ts 파일을 하나 작성 해준다.

 

백엔드 코드에서 엔드포인트 쪽에다가 인터셉터를 주입해준다~!

업데이트 등 될경우에는 

아래와 같이 레디스 명령어를 사용해서 지정해둔 키값을 삭제 해주고 업데이트 쳐준다.

업데이트 후 데이터가 남아있는 오류는 면할 수 있다.

 

다음에는 추가로 레디스를 활용해서 이미지 캐시 처리도 진행 해보겠다.

 

사이트 접속 -> Stable version 설치

nginx: download

NGINX 압축 해제

C:\Program Files\nginx-1.26.1 -> 경로에 압축 해제

nginx를 설치를 완료 후에는 nssm을 설치 해야합니다.

서비스 등록을 해야 컴퓨터 재부팅 후 자동 서버 재실행을 위한 설치 입니다.

사이트 접속 -> Latest release 설치

NSSM - the Non-Sucking Service Manager

C:\Program Files\nssm-2.24 위치에 설치

CD C:\Program Files\nssm-2.24\win64 이동 후

 

nssm install nginx 입력

전체 설치가 끝 난 후에는 nginx conf 파일 수정을 합니다.

 

직접 수정 시

윈도우 검색 창 -> 워드패드 관리자 권한 실행 후
C:\Program Files\nginx-1.26.1\conf
열기

 

user  nobody;
worker_processes  auto;

error_log  logs/error.log;
pid        logs/nginx.pid;

events {
    worker_connections  4096;  # 동시 연결 수 증가
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    access_log  logs/access.log;

    gzip  on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_comp_level 5;

    # 첫 번째 백엔드 서버 정의
    upstream backend_server_1 {
        server 192.168.0.131:3002;
    }

    # 두 번째 백엔드 서버 정의
    upstream backend_server_2 {
        server 192.168.0.131:3003;
    }

    # 포트 80에서 수신되는 요청을 처리하는 서버
    server {
        listen       80;
        server_name  localhost;

        root C:/workspace/testdist/frontend_80;
 
        location /api/ {
            proxy_pass         http://backend_server_1;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            
            proxy_http_version 1.1;
            # WebSocket 지원 설정
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
            proxy_connect_timeout 300s;
        }

        location /socket.io/ {
            proxy_pass         http://backend_server_1;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            
            proxy_http_version 1.1;
            # WebSocket 지원 설정
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
            proxy_connect_timeout 300s;
        }

        location / {
            try_files $uri $uri/ /index.html;
            proxy_pass         http://backend_server_1;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            
            proxy_http_version 1.1;
            # WebSocket 지원 설정
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
            proxy_connect_timeout 300s;
        }
    }

    # 포트 81에서 수신되는 요청을 처리하는 서버
    server {
        listen       81;
        server_name  localhost;

        location /api/ {
            proxy_pass         http://backend_server_2;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            
            proxy_http_version 1.1;
            # WebSocket 지원 설정
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
            proxy_connect_timeout 300s;
        
}

        location /socket.io/ {
            proxy_pass         http://backend_server_2;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            
            proxy_http_version 1.1;
            # WebSocket 지원 설정
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
            proxy_connect_timeout 300s;
        }

        location / {
            proxy_pass         http://backend_server_2;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            
            proxy_http_version 1.1;
            # WebSocket 지원 설정
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
            proxy_connect_timeout 300s;
        }
    }
}

 

위와 같이 작성 웹소켓 사용으로 등 여러가지 옵션 추가 후 저장

Nginx 서버 실행 서비스로 실행 하기

C:\Program Files\nssm-2.24\win64 nssm이 위치하는 폴더를 위치로 관리자모드 cmd 창에 nssm start nginx 명령어를 칩니다.

위 와 같이 작업 완료를 확인 후 

port 방화벽 인바운드 규칙에 추가

# Windows 방화벽 명령어를 칩니다. 인 바운드 규칙 들어간 후 제대로 추가가 됐는지 확인 안 되어있으면 새 규칙으로 추가.

netsh advfirewall firewall add rule name="Open Port 3002" dir=in action=allow protocol=TCP localport=3002

netsh advfirewall firewall add rule name="Open Port 8080" dir=in action=allow protocol=TCP localport=8080

netsh advfirewall firewall add rule name="Open Port 80" dir=in action=allow protocol=TCP localport=80

 

 

# 정상 작동 확인

curl -I http://192.168.0.131:3002/

 

 

최종확인

# 요청url /api 호출 시 프록시 서버에서 3002로 정상 전달되는지 확인

 

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

nginx 셋팅 (개발서버)  (0) 2023.01.13

AWS S3 Bucket 생성:

AWS 콘솔에 로그인한 후, S3 서비스로 이동한다.
"Create bucket" 버튼을 클릭하여 새로운 버킷을 생성합니다. 버킷 이름은 유니크해야 한다.

 

버킷 생성

생성 후 

정적 웹 사이트 호스팅 활성화로 변경을 해준다.

빌드한 파일을 업로드 한다.

 

업로드 후 

버킷 정책을 생성하는 곳에 들어가서 내 아이디 - 최상단 우측에 숫자를 기입 (Principal)

arn 은 버킷 정책 수정 클릭하면 나옴

 

전체 기입 후 완료를 눌러주고 생성된 url 로 들어간다.

 

액세스 권한 오류가 뜬다.

이문제법 해결로는 원본도메인을 수정해주면 된다.

변경 해준다.

 

 

접속하면 접근가능하다.

'Cloud > Aws' 카테고리의 다른 글

서버와 클라우드 컴퓨팅 Aws  (1) 2023.12.12

프로젝트 생성

npx create-react-app toyproject

 

패키지 설치

npm install tailwindcss postcss-cli autoprefixer

 

css 설정파일 생성 ( tailwind.config.js)

npx tailwindcss init -p

 

PostCSS 설정 파일 생성

notepad postcss.config.js

 

내용 추가

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

 

index.css

@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';


body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

 

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
      extend: {},
  },
  plugins: [],
};

 

추가적으로 만들기 전 라우터 사용하기 위해서 설치

npm install react-router-dom

 

// import logo from './logo.svg';
import './App.css';
import React from 'react';
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Main from './pages/main';
// import About from './pages/About';



function App() {
  return (
    <BrowserRouter>
      {/* <Header /> */}
      <Routes>
        <Route path="/" element={<Main />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

 

만들어준 후 공통 header.tsx 작업을 들어간다.

 

일단 메인쪽에서 애니메이션 효과를 사용할 것이기에 해당 npm 을 설치한다.

npm install react-scroll framer-motion

 

import React from 'react';
import { Link } from 'react-router-dom';

const Header: React.FunctionComponent = () => {
  return (
    <header>
        <h2 className="dn none">상단메뉴</h2>
        <div>
            <button>견적문의</button>
            <p>000-0000-0000</p>
        </div>
      <nav>
        <ul>
          <li>
            <Link to="/">Main</Link>
          </li>
          <li>
            <Link to="/about">회사 소개</Link>
          </li>
          <li>
            <Link to="/about">사업 현황</Link>
          </li>
          <li>
            <Link to="/about">파트너사 문의</Link>
          </li>
        </ul>
      </nav>
    </header>
  );
};

export default Header;

헤더 부분 만들어주고 좀있다가 테일윈드css 를 적용해보겠다.

공식 문서 

https://tailwindcss.com/docs/installation

 

Installation - Tailwind CSS

The simplest and fastest way to get up and running with Tailwind CSS from scratch is with the Tailwind CLI tool.

tailwindcss.com

 

여기서 필요한 부분을 확인하면서 추가를 하도록 하겠다.

정렬을 위해서 해당 클래스를 넣어주면

 

이런형태로 들어간다.

 

 

이런식으로 적용이 되는걸 확인 할 수 있다. 

추후에 개인 프로젝트로 하나 만들예정이다. 

 

깃허브

https://github.com/asdf132645/finish

+ Recent posts