diff --git a/packages/backend/src/chat/like.service.ts b/packages/backend/src/chat/like.service.ts index a1495905..bead22b3 100644 --- a/packages/backend/src/chat/like.service.ts +++ b/packages/backend/src/chat/like.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable } from '@nestjs/common'; +import { Cron } from '@nestjs/schedule'; import { DataSource, EntityManager } from 'typeorm'; import { Chat } from '@/chat/domain/chat.entity'; import { Like } from '@/chat/domain/like.entity'; @@ -6,6 +7,8 @@ import { LikeResponse } from '@/chat/dto/like.response'; @Injectable() export class LikeService { + private readonly likedChats = new Set(); + constructor(private readonly dataSource: DataSource) {} async toggleLike(userId: number, chatId: number) { @@ -14,6 +17,7 @@ export class LikeService { const like = await manager.findOne(Like, { where: { user: { id: userId }, chat: { id: chatId } }, }); + this.likedChats.add(chatId); if (like) { return await this.deleteLike(manager, chat, like); } @@ -21,6 +25,24 @@ export class LikeService { }); } + @Cron('*/30 * * * *') + async calculateLike() { + const tasks = []; + for (const chatId of this.likedChats) { + tasks.push(async () => { + const likeCount = await this.dataSource + .getRepository(Like) + .createQueryBuilder('like') + .where('like.chat_id = :chatId', { chatId }) + .getCount(); + + await this.dataSource.getRepository(Chat).update(chatId, { likeCount }); + }); + } + await Promise.all(tasks.map((task) => task())); + this.likedChats.clear(); + } + private async findChat(chatId: number, manager: EntityManager) { const chat = await manager.findOne(Chat, { where: { id: chatId }, diff --git a/packages/backend/src/common/cache/localCache.ts b/packages/backend/src/common/cache/localCache.ts index 03a7a6fb..b28aec1d 100644 --- a/packages/backend/src/common/cache/localCache.ts +++ b/packages/backend/src/common/cache/localCache.ts @@ -16,7 +16,7 @@ export class LocalCache { new PriorityQueue(); constructor(private readonly interval = 500) { - setInterval(() => this.clearExpired(), interval); + setInterval(() => this.clearExpired(), this.interval); } get(key: K) { diff --git a/packages/backend/src/stock/cache/stockData.cache.ts b/packages/backend/src/stock/cache/stockData.cache.ts index 5a1ea479..5158f473 100644 --- a/packages/backend/src/stock/cache/stockData.cache.ts +++ b/packages/backend/src/stock/cache/stockData.cache.ts @@ -1,16 +1,16 @@ import { Injectable } from '@nestjs/common'; import { LocalCache } from '@/common/cache/localCache'; -import { StockDataResponse } from '@/stock/dto/stockData.response'; +import { StockData } from '@/stock/domain/stockData.entity'; @Injectable() export class StockDataCache { - private readonly localCache = new LocalCache(); + private readonly localCache = new LocalCache(); - set(key: string, value: StockDataResponse, ttl: number = 60000) { + set(key: string, value: StockData[], ttl: number = 60000) { this.localCache.set(key, value, ttl); } - get(key: string): StockDataResponse | null { + get(key: string): StockData[] | null { return this.localCache.get(key); } } diff --git a/packages/backend/src/stock/dto/stockData.response.ts b/packages/backend/src/stock/dto/stockData.response.ts index f251aa6d..09b9d643 100644 --- a/packages/backend/src/stock/dto/stockData.response.ts +++ b/packages/backend/src/stock/dto/stockData.response.ts @@ -21,32 +21,32 @@ export class PriceDto { description: '시가', example: '121.00', }) - open: number; + open: string; @ApiProperty({ description: '고가', example: '125.00', }) - high: number; + high: string; @ApiProperty({ description: '저가', example: '120.00', }) - low: number; + low: string; @ApiProperty({ description: '종가', example: '123.45', }) - close: number; + close: string; constructor(stockData: StockData) { this.startTime = stockData.startTime; - this.open = stockData.open; - this.high = stockData.high; - this.low = stockData.low; - this.close = stockData.close; + this.open = String(stockData.open); + this.high = String(stockData.high); + this.low = String(stockData.low); + this.close = String(stockData.close); } } @@ -104,20 +104,36 @@ export class StockDataResponse { renewLastData(stockLiveData: StockLiveData, entity: new () => StockData) { const lastIndex = this.priceDtoList.length - 1; - this.priceDtoList[lastIndex].close = stockLiveData.currentPrice; + this.priceDtoList[lastIndex].close = String(stockLiveData.currentPrice); + this.renewHighLow(stockLiveData, lastIndex); + this.renewVolume(stockLiveData, lastIndex, entity); + this.renewStartTime(entity, lastIndex); + } + + private renewStartTime(entity: new () => StockData, lastIndex: number) { + if (entity !== StockWeekly) { + this.priceDtoList[lastIndex].startTime = getToday(); + this.volumeDtoList[lastIndex].startTime = getToday(); + return; + } + } + + private renewHighLow(stockLiveData: StockLiveData, lastIndex: number) { this.priceDtoList[lastIndex].high = Number(stockLiveData.high) > Number(this.priceDtoList[lastIndex].high) - ? stockLiveData.high - : this.priceDtoList[lastIndex].high; + ? String(stockLiveData.high) + : String(this.priceDtoList[lastIndex].high); this.priceDtoList[lastIndex].low = Number(stockLiveData.low) < Number(this.priceDtoList[lastIndex].low) - ? stockLiveData.low - : this.priceDtoList[lastIndex].low; + ? String(stockLiveData.low) + : String(this.priceDtoList[lastIndex].low); + } - this.priceDtoList[lastIndex].startTime = - entity !== StockWeekly - ? getToday() - : this.priceDtoList[lastIndex].startTime; + private renewVolume( + stockLiveData: StockLiveData, + lastIndex: number, + entity: new () => StockData, + ) { this.volumeDtoList[lastIndex].volume = entity === StockDaily ? String(stockLiveData.volume) diff --git a/packages/backend/src/stock/stockData.service.ts b/packages/backend/src/stock/stockData.service.ts index 3527b240..ec3dbd8a 100644 --- a/packages/backend/src/stock/stockData.service.ts +++ b/packages/backend/src/stock/stockData.service.ts @@ -56,10 +56,20 @@ export class StockDataService { const cachedData = this.stockDataCache.get(cacheKey); if (cachedData) { - return cachedData; + const time = new Date(); + const response = this.convertResultsToResponse(cachedData); + if (!lastStartTime && time.getHours() < 16 && time.getHours() >= 9) { + return await this.renewResponse(response, entity, stockId); + } + return response; + } + const results = await this.getChartData(entity, stockId, lastStartTime); + this.stockDataCache.set(cacheKey, results); + const response = this.convertResultsToResponse(results); + const time = new Date(); + if (!lastStartTime && time.getHours() < 16 && time.getHours() >= 9) { + return await this.renewResponse(response, entity, stockId); } - const response = await this.getChartData(entity, stockId, lastStartTime); - this.stockDataCache.set(cacheKey, response); return response; } @@ -69,7 +79,7 @@ export class StockDataService { periodType: Period, lastStartTime?: string, ) { - return new Promise((resolve) => { + return new Promise((resolve) => { this.openapiPeriodData.insertCartDataRequest( this.getHandleResponseCallback(entity, stockId, resolve, lastStartTime), stockId, @@ -109,16 +119,7 @@ export class StockDataService { lastStartTime, ); } - const response = await this.getChartDataFromDB( - entity, - stockId, - lastStartTime, - ); - const time = new Date(); - if (!lastStartTime && time.getHours() < 16 && time.getHours() >= 9) { - return await this.renewResponse(response, entity, stockId); - } - return response; + return await this.getChartDataFromDB(entity, stockId, lastStartTime); } private async renewResponse( @@ -146,14 +147,13 @@ export class StockDataService { this.dataSource.manager, lastStartTime, ); - const results = await queryBuilder.getMany(); - return this.convertResultsToResponse(results); + return await queryBuilder.getMany(); } private getHandleResponseCallback( entity: new () => StockData, stockId: string, - resolve: (value: StockDataResponse) => void, + resolve: (value: StockData[]) => void, lastStartTime?: string, ) { return async () => {