From 635f76f9b7b9531909a92526e60b37bf3db864dc Mon Sep 17 00:00:00 2001 From: dannysir <48199716+dannysir@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:23:13 +0900 Subject: [PATCH 1/9] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d2c4d7..345078e 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,14 @@ - 일별, 실시간 시세를 확인할 수 있습니다. - 매수, 매도 요청을 할 수 있습니다. -### 주식 차트 +### 주식 차트 ![화면 기록 2024-11-28 오후 6 40 30](https://github.com/user-attachments/assets/6d36b0d9-2db2-4018-a7f3-2c12fb586fd0) - 일, 주, 월, 년 단위로 주식 차트를 확인할 수 있습니다. - 이동평균선 정보를 활용해 해당 주식의 추이를 더 자세히 확인할 수 있습니다. - 라이브러리를 사용하지 않고 canvas를 활용해 직접 구현했습니다. +- [라이브러리 없이 구현한 이유.](https://github.com/boostcampwm-2024/web16-JuGa/wiki/라이브러리-없이-차트를-직접-구현한-이유) + ### 마이페이지 ![juga_mypage](https://github.com/user-attachments/assets/8cdfa089-ac26-40a0-8d19-7a56a0f3c6e7) From 1acdd10102e18018df7428821a1575de8a93ec53 Mon Sep 17 00:00:00 2001 From: Dongwoo Ko <68095803+dongree@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:57:58 +0900 Subject: [PATCH 2/9] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 345078e..52f30c2 100644 --- a/README.md +++ b/README.md @@ -39,21 +39,21 @@ ### 메인 페이지 -![juga_main](https://github.com/user-attachments/assets/abb04197-ae88-4877-be53-2ca71ad3e57b) + - 메인 페이지에서 코스피, 코스닥 등 실시간 주가 지수를 확인할 수 있습니다. - 상승률/하락률 TOP5 종목을 주가지수 별로 확인할 수 있습니다. - 오늘 실시간 주요 뉴스를 확인할 수 있습니다. ### 주식 상세 페이지 -![juga_detail](https://github.com/user-attachments/assets/14ed36ae-085e-4899-a314-8ece85236a55) + - 해당 주식에 대한 정보를 차트로 확인할 수 있습니다. - 일별, 실시간 시세를 확인할 수 있습니다. - 매수, 매도 요청을 할 수 있습니다. ### 주식 차트 -![화면 기록 2024-11-28 오후 6 40 30](https://github.com/user-attachments/assets/6d36b0d9-2db2-4018-a7f3-2c12fb586fd0) + - 일, 주, 월, 년 단위로 주식 차트를 확인할 수 있습니다. - 이동평균선 정보를 활용해 해당 주식의 추이를 더 자세히 확인할 수 있습니다. @@ -62,7 +62,7 @@ ### 마이페이지 -![juga_mypage](https://github.com/user-attachments/assets/8cdfa089-ac26-40a0-8d19-7a56a0f3c6e7) + - 현재 자산 현황, 투자 성과를 확인할 수 있습니다. - 자신이 매수한 주식 정보들을 확인할 수 있습니다. @@ -72,14 +72,14 @@ ### 로그인 -![image (14)](https://github.com/user-attachments/assets/9968ef08-cbf8-41fd-bfdc-8ca25dd8d80c) + - 로그인 모달창에서 로그인을 할 수 있습니다. - 카카오 oAuth 로그인으로 간편하게 로그인할 수 있습니다. ### 랭킹 -![image (15)](https://github.com/user-attachments/assets/251821a9-63d9-4f23-9178-2f8f3d8c608d) + - 하루 단위로 랭킹이 갱신됩니다. - 수익률순, 자산순을 기준으로 랭킹을 확인할 수 있습니다. From fcf2cb5b85dd86d3692076ab24468c5577f4bce6 Mon Sep 17 00:00:00 2001 From: jinddings Date: Tue, 3 Dec 2024 18:01:42 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=94=A7=20fix=20:=20=EC=9B=B9=EC=86=8C?= =?UTF-8?q?=EC=BC=93=20=EC=97=B0=EA=B2=B0=20=EC=B4=88=EA=B3=BC=EC=8B=9C=20?= =?UTF-8?q?error=20=EB=B0=9C=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/common/websocket/base-socket.domain-service.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/BE/src/common/websocket/base-socket.domain-service.ts b/BE/src/common/websocket/base-socket.domain-service.ts index 527bf9f..2b3f9f0 100644 --- a/BE/src/common/websocket/base-socket.domain-service.ts +++ b/BE/src/common/websocket/base-socket.domain-service.ts @@ -45,11 +45,18 @@ export class BaseSocketDomainService implements OnModuleInit { if (data.length < 2) { const json = JSON.parse(data[0]); - if (json.body) + if (json.body) { this.logger.log( `한국투자증권 웹소켓 연결: ${json.body.msg1}`, json.header.tr_id, ); + + if (json.body.msg1 === 'MAX SUBSCRIBE OVER') { + throw new InternalServerErrorException( + '한국투자증권 웹소켓 연결: MAX SUBSCRIBE OVER', + ); + } + } if (json.header.tr_id === 'PINGPONG') this.socket.pong(JSON.stringify(json)); return; From 6608f7e24b8c6722fe204dc332a7d6e8be302e07 Mon Sep 17 00:00:00 2001 From: jinddings Date: Tue, 3 Dec 2024 18:34:43 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=9A=91=20!HOTFIX=20:=20socket=20gate?= =?UTF-8?q?=20way=20filter=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filters/websocket-exception.filter.ts | 39 +++++++++++++++++++ BE/src/common/websocket/socket.gateway.ts | 4 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 BE/src/common/filters/websocket-exception.filter.ts diff --git a/BE/src/common/filters/websocket-exception.filter.ts b/BE/src/common/filters/websocket-exception.filter.ts new file mode 100644 index 0000000..349bfbd --- /dev/null +++ b/BE/src/common/filters/websocket-exception.filter.ts @@ -0,0 +1,39 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpStatus, + Logger, + InternalServerErrorException, +} from '@nestjs/common'; + +import { Request, Response } from 'express'; + +@Catch(InternalServerErrorException) +export class WebSocketExceptionFilter implements ExceptionFilter { + private readonly logger = new Logger(WebSocketExceptionFilter.name); + + catch(exception: InternalServerErrorException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + + const request = ctx.getRequest(); + const response = ctx.getResponse(); + + const status = HttpStatus.INTERNAL_SERVER_ERROR; + + const { message } = exception; + + const errorResponse = { + statusCode: status, + message, + timestamp: new Date().toISOString(), + path: request.url, + }; + + this.logger.error( + `[${request.method}] ${request.url} - Status: ${status} - Error: ${exception.message}`, + ); + + response.status(status).json(errorResponse); + } +} diff --git a/BE/src/common/websocket/socket.gateway.ts b/BE/src/common/websocket/socket.gateway.ts index d533c3f..b898fda 100644 --- a/BE/src/common/websocket/socket.gateway.ts +++ b/BE/src/common/websocket/socket.gateway.ts @@ -1,8 +1,10 @@ import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; import { Server } from 'socket.io'; -import { Logger } from '@nestjs/common'; +import { Logger, UseFilters } from '@nestjs/common'; +import { WebSocketExceptionFilter } from '../filters/websocket-exception.filter'; @WebSocketGateway({ namespace: 'socket', cors: { origin: '*' } }) +@UseFilters(new WebSocketExceptionFilter()) export class SocketGateway { @WebSocketServer() private server: Server; From b7559f7d95deb0502f6b83bb33768df2d6ce6727 Mon Sep 17 00:00:00 2001 From: jinddings Date: Tue, 3 Dec 2024 18:46:13 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=94=A7=20fix=20:=20Websocket=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filters/websocket-exception.filter.ts | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/BE/src/common/filters/websocket-exception.filter.ts b/BE/src/common/filters/websocket-exception.filter.ts index 349bfbd..9a81ccd 100644 --- a/BE/src/common/filters/websocket-exception.filter.ts +++ b/BE/src/common/filters/websocket-exception.filter.ts @@ -1,39 +1,28 @@ -import { - ArgumentsHost, - Catch, - ExceptionFilter, - HttpStatus, - Logger, - InternalServerErrorException, -} from '@nestjs/common'; +import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common'; +import { WsException } from '@nestjs/websockets'; -import { Request, Response } from 'express'; - -@Catch(InternalServerErrorException) +@Catch() export class WebSocketExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(WebSocketExceptionFilter.name); - catch(exception: InternalServerErrorException, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - - const request = ctx.getRequest(); - const response = ctx.getResponse(); - - const status = HttpStatus.INTERNAL_SERVER_ERROR; + catch(exception: Error, host: ArgumentsHost) { + const ctx = host.switchToWs(); + const client = ctx.getClient(); - const { message } = exception; + const message = + exception instanceof WsException + ? exception.message + : 'Internal WebSocket Error'; const errorResponse = { - statusCode: status, + event: 'error', message, timestamp: new Date().toISOString(), - path: request.url, }; - this.logger.error( - `[${request.method}] ${request.url} - Status: ${status} - Error: ${exception.message}`, - ); + this.logger.error(`WebSocket Error: ${message}`, exception.stack); - response.status(status).json(errorResponse); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + client.send(JSON.stringify(errorResponse)); } } From 3b89940f98c954fb0d674b9cb465a49cd18b3f8b Mon Sep 17 00:00:00 2001 From: JIN <80706216+uuuo3o@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:40:28 +0900 Subject: [PATCH 6/9] Update README.md --- README.md | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/README.md b/README.md index 52f30c2..a366266 100644 --- a/README.md +++ b/README.md @@ -59,32 +59,6 @@ - 이동평균선 정보를 활용해 해당 주식의 추이를 더 자세히 확인할 수 있습니다. - 라이브러리를 사용하지 않고 canvas를 활용해 직접 구현했습니다. - [라이브러리 없이 구현한 이유.](https://github.com/boostcampwm-2024/web16-JuGa/wiki/라이브러리-없이-차트를-직접-구현한-이유) - - -### 마이페이지 - - -- 현재 자산 현황, 투자 성과를 확인할 수 있습니다. -- 자신이 매수한 주식 정보들을 확인할 수 있습니다. -- 주문 요청 현황 탭에서 주문 요청한 주식들을 확인하고 요청을 취소할 수 있습니다. -- 즐겨찾기 탭에서 주식 상세 페이지에서 좋아요한 주식들을 확인할 수 있습니다. -- 내 정보 탭에서 자신의 닉네임을 변경할 수 있습니다. - - -### 로그인 - - -- 로그인 모달창에서 로그인을 할 수 있습니다. -- 카카오 oAuth 로그인으로 간편하게 로그인할 수 있습니다. - - -### 랭킹 - - -- 하루 단위로 랭킹이 갱신됩니다. -- 수익률순, 자산순을 기준으로 랭킹을 확인할 수 있습니다. -- 자신의 오늘 랭킹을 확인할 수 있습니다. - ## 🏛️ 소프트웨어 아키텍처 소프트웨어 아키텍처 3 0 From 799f87c6c4a526387572fe7511a7bbd98bfbd1fd Mon Sep 17 00:00:00 2001 From: jinddings Date: Tue, 3 Dec 2024 19:44:43 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=94=A7=20fix=20:=20web=20socket=20err?= =?UTF-8?q?or=20=ED=95=84=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filters/websocket-exception.filter.ts | 28 ------------------- .../websocket/base-socket.domain-service.ts | 6 ---- BE/src/common/websocket/socket.gateway.ts | 4 +-- .../history/stock-trade-history.service.ts | 9 ++++-- 4 files changed, 8 insertions(+), 39 deletions(-) delete mode 100644 BE/src/common/filters/websocket-exception.filter.ts diff --git a/BE/src/common/filters/websocket-exception.filter.ts b/BE/src/common/filters/websocket-exception.filter.ts deleted file mode 100644 index 9a81ccd..0000000 --- a/BE/src/common/filters/websocket-exception.filter.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common'; -import { WsException } from '@nestjs/websockets'; - -@Catch() -export class WebSocketExceptionFilter implements ExceptionFilter { - private readonly logger = new Logger(WebSocketExceptionFilter.name); - - catch(exception: Error, host: ArgumentsHost) { - const ctx = host.switchToWs(); - const client = ctx.getClient(); - - const message = - exception instanceof WsException - ? exception.message - : 'Internal WebSocket Error'; - - const errorResponse = { - event: 'error', - message, - timestamp: new Date().toISOString(), - }; - - this.logger.error(`WebSocket Error: ${message}`, exception.stack); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - client.send(JSON.stringify(errorResponse)); - } -} diff --git a/BE/src/common/websocket/base-socket.domain-service.ts b/BE/src/common/websocket/base-socket.domain-service.ts index 2b3f9f0..5f77220 100644 --- a/BE/src/common/websocket/base-socket.domain-service.ts +++ b/BE/src/common/websocket/base-socket.domain-service.ts @@ -50,12 +50,6 @@ export class BaseSocketDomainService implements OnModuleInit { `한국투자증권 웹소켓 연결: ${json.body.msg1}`, json.header.tr_id, ); - - if (json.body.msg1 === 'MAX SUBSCRIBE OVER') { - throw new InternalServerErrorException( - '한국투자증권 웹소켓 연결: MAX SUBSCRIBE OVER', - ); - } } if (json.header.tr_id === 'PINGPONG') this.socket.pong(JSON.stringify(json)); diff --git a/BE/src/common/websocket/socket.gateway.ts b/BE/src/common/websocket/socket.gateway.ts index b898fda..d533c3f 100644 --- a/BE/src/common/websocket/socket.gateway.ts +++ b/BE/src/common/websocket/socket.gateway.ts @@ -1,10 +1,8 @@ import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; import { Server } from 'socket.io'; -import { Logger, UseFilters } from '@nestjs/common'; -import { WebSocketExceptionFilter } from '../filters/websocket-exception.filter'; +import { Logger } from '@nestjs/common'; @WebSocketGateway({ namespace: 'socket', cors: { origin: '*' } }) -@UseFilters(new WebSocketExceptionFilter()) export class SocketGateway { @WebSocketServer() private server: Server; diff --git a/BE/src/stock/trade/history/stock-trade-history.service.ts b/BE/src/stock/trade/history/stock-trade-history.service.ts index 8ef3b26..7fa7e60 100644 --- a/BE/src/stock/trade/history/stock-trade-history.service.ts +++ b/BE/src/stock/trade/history/stock-trade-history.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { KoreaInvestmentDomainService } from '../../../common/koreaInvestment/korea-investment.domain-service'; import { InquireCCNLApiResponse } from './interface/Inquire-ccnl.interface'; import { TodayStockTradeHistoryOutputDto } from './dto/today-stock-trade-history-output.dto'; @@ -35,7 +35,12 @@ export class StockTradeHistoryService { queryParams, ); - this.stockPriceSocketService.subscribeByCode(stockCode); + try { + this.stockPriceSocketService.subscribeByCode(stockCode); + } catch (e) { + throw new InternalServerErrorException(e); + } + return this.formatTodayStockTradeHistoryData(response.output); } From 6d70a3cb3f12c2ddaaf152d00acef36fb555ded8 Mon Sep 17 00:00:00 2001 From: sieun <147706431+sieunie@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:11:05 +0900 Subject: [PATCH 8/9] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a366266..969d460 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,14 @@ - 일, 주, 월, 년 단위로 주식 차트를 확인할 수 있습니다. - 이동평균선 정보를 활용해 해당 주식의 추이를 더 자세히 확인할 수 있습니다. - 라이브러리를 사용하지 않고 canvas를 활용해 직접 구현했습니다. -- [라이브러리 없이 구현한 이유.](https://github.com/boostcampwm-2024/web16-JuGa/wiki/라이브러리-없이-차트를-직접-구현한-이유) +- [라이브러리 없이 구현한 이유](https://github.com/boostcampwm-2024/web16-JuGa/wiki/라이브러리-없이-차트를-직접-구현한-이유) ## 🏛️ 소프트웨어 아키텍처 소프트웨어 아키텍처 3 0 +- 한국투자증권 웹소켓은 한 계좌 당 41개의 종목에 대한 구독만을 유지할 수 있기 때문에, 최대한 많은 구독을 가능하게 하기 위한 방법으로 `Load Balancing`을 선택했습니다. +- 서버의 각 컨테이너는 모두 다른 계좌로 연결되어 총 `41*3`개의 구독을 유지할 수 있습니다. +- 추가로, redis의 pub/sub을 활용하여 서로 다른 서버로 요청이 들어오더라도 같은 종목에 대한 구독은 하나의 서버에서만 관리하도록 구현해 구독 자원을 최대한 절약하도록 했습니다. ## 🧑🏻 팀원 소개 | 🖥️ Web FE | ⚙️ Web BE | ⚙️ Web BE | 🖥️ Web FE | ⚙️ Web BE | From 1b1f8fac8161d22bbd8d5c686863fccb751ef714 Mon Sep 17 00:00:00 2001 From: jinddings Date: Tue, 3 Dec 2024 20:59:56 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=9A=91=20!HOTFIX=20:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=90=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20200=20status,=20isLogin?= =?UTF-8?q?=20False=20=EB=A1=9C=20=EC=9D=91=EB=8B=B5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/auth/auth.controller.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index 88c7ad5..fbb629e 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -15,6 +15,7 @@ import { Request, Response } from 'express'; import { ConfigService } from '@nestjs/config'; import { AuthService } from './auth.service'; import { AuthCredentialsDto } from './dto/auth-credentials.dto'; +import { OptionalAuthGuard } from './optional-auth-guard'; @Controller('/api/auth') export class AuthController { @@ -83,13 +84,14 @@ export class AuthController { @ApiOperation({ summary: '로그인 상태 확인 API' }) @Get('/check') - @UseGuards(AuthGuard('jwt')) + @UseGuards(OptionalAuthGuard) @ApiResponse({ status: 200, description: '로그인 상태 조회 성공', example: { isLogin: true }, }) - check() { + check(@Req() req: Request) { + if (!req.user) return { isLogin: false }; return { isLogin: true }; }