Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] redis를 사용해 최근 검색어 저장 #105

Merged
merged 25 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2ee713d
Merge branch 'back/main' of https://github.com/boostcampwm-2024/web16…
jinddings Nov 13, 2024
ea0afd5
Merge branch 'back/main' of https://github.com/boostcampwm-2024/web16…
jinddings Nov 13, 2024
0afe5b1
✨ feat : redis를 이용해 최근 검색 단어 저장(#57):
jinddings Nov 13, 2024
a617038
✨ feat: API 분리를 위해 chart 전용으로 변경#54
uuuo3o Nov 13, 2024
e23424b
✨ feat: 프론트에서 필요한 값만 반환할 수 있도록 DTO 수정#54
uuuo3o Nov 13, 2024
0203e65
🎨 style: 코드 순서 변경#54
uuuo3o Nov 13, 2024
00677ba
🔥 remove: 불필요한 파일 삭제#54
uuuo3o Nov 13, 2024
383a166
📝 docs: 파일명 변경 및 swagger 문서 수정#54
uuuo3o Nov 13, 2024
463c4cd
➕ add: 차트 그리기에 필요한 값 추가 및 인터페이스 이름 변경#54
uuuo3o Nov 13, 2024
764de7b
🚚 rename: 중복되는 이름 변경#54
uuuo3o Nov 13, 2024
98b8b57
✨ feat: 주식 현재가 API에 사용할 interface, dto 구현#54
uuuo3o Nov 13, 2024
4fe008b
✨ feat: 주식 현재가 API 요청 로직 구현#54
uuuo3o Nov 13, 2024
7dd9da8
♻️ refactor: requestApi 함수 분리 및 불필요한 파일 삭제#55
uuuo3o Nov 13, 2024
6df873c
🚚 rename: 이름을 명확하게 변경#55
uuuo3o Nov 13, 2024
875f2a7
✨ feat: 주식현재가 일자별 API에 사용할 interface, dto 구현#55
uuuo3o Nov 13, 2024
e56921a
✨ feat: 주식현재가 일자별 API 요청 로직 구현#55
uuuo3o Nov 13, 2024
7a61c8d
🚑 !HOTFIX : 도커 컨테이너 시간 설정
jinddings Nov 14, 2024
850e53b
🚑 !HOTFIX : docker 시간 설정 명령어 수정
jinddings Nov 14, 2024
5eb4fc5
🚑 !HOTFIX : docker 시간 설정 명령어 수정
jinddings Nov 14, 2024
e028917
🔧 fix : auth api 경로 변경(#57)
jinddings Nov 14, 2024
d218da8
🚚 rename : redisUtil -> redisDomainService로 클래스 및 파일 이름 변경(#57)
jinddings Nov 14, 2024
3bb0365
✨ feat : 최근 검색어 10 개만 저장하도록 수정(#57)
jinddings Nov 14, 2024
2b2b642
Merge branch 'back/main' into feature/api/stockList-#57
jinddings Nov 14, 2024
b19180c
♻️ refactor : lint 오류 수정(#57)
jinddings Nov 14, 2024
6bd2c54
Merge branch 'feature/api/stockList-#57' of https://github.com/boostc…
jinddings Nov 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions BE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions BE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"dotenv": "^16.4.5",
"express": "^4.21.1",
"fastify-swagger": "^5.1.1",
"ioredis": "^5.4.1",
"mysql2": "^3.11.3",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
Expand Down
2 changes: 2 additions & 0 deletions BE/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { StockDetailModule } from './stock/detail/stock-detail.module';
import { typeOrmConfig } from './configs/typeorm.config';
import { StockListModule } from './stock/list/stock-list.module';
import { StockTradeHistoryModule } from './stock/trade/history/stock-trade-history.module';
import { RedisModule } from './common/redis/redis.module';

@Module({
imports: [
Expand All @@ -29,6 +30,7 @@ import { StockTradeHistoryModule } from './stock/trade/history/stock-trade-histo
StockOrderModule,
StockListModule,
StockTradeHistoryModule,
RedisModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
7 changes: 6 additions & 1 deletion BE/src/auth/strategy/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
const user: User = await this.userRepository.findOne({ where: { email } });
if (!user) throw new UnauthorizedException();

return user;
return {
userId: user.id,
email: user.email,
tutorial: user.tutorial,
kakaoId: user.kakaoId,
};
}
}
57 changes: 57 additions & 0 deletions BE/src/common/redis/redis.domain-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Injectable, Inject } from '@nestjs/common';
import Redis from 'ioredis';

@Injectable()
export class RedisDomainService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 바꾸셨네용 좋습니다!! 저도 참고하겠습니다

constructor(
@Inject('REDIS_CLIENT')
private readonly redis: Redis,
) {}

async get(key: string): Promise<string | null> {
return this.redis.get(key);
}

async set(key: string, value: string, expires?: number): Promise<'OK'> {
if (expires) {
return this.redis.set(key, value, 'EX', expires);
}
return this.redis.set(key, value);
}

async del(key: string): Promise<number> {
return this.redis.del(key);
}

async zadd(key: string, score: number, member: string): Promise<number> {
return this.redis.zadd(key, score, member);
}

async zcard(key: string): Promise<number> {
return this.redis.zcard(key);
}

async zrange(key: string, start: number, stop: number): Promise<string[]> {
return this.redis.zrange(key, start, stop);
}

async zremrangebyrank(
key: string,
start: number,
stop: number,
): Promise<number> {
return this.redis.zremrangebyrank(key, start, stop);
}

async zrevrange(key: string, start: number, stop: number): Promise<string[]> {
return this.redis.zrevrange(key, start, stop);
}

async zrem(key: string, member: string): Promise<number> {
return this.redis.zrem(key, member);
}

async expire(key: string, seconds: number): Promise<number> {
return this.redis.expire(key, seconds);
}
}
22 changes: 22 additions & 0 deletions BE/src/common/redis/redis.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// src/common/redis/redis.module.ts
import { Global, Module } from '@nestjs/common';
import Redis from 'ioredis';
import { RedisDomainService } from './redis.domain-service';

@Global()
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: () => {
return new Redis({
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
});
},
},
RedisDomainService,
],
exports: [RedisDomainService, 'REDIS_CLIENT'],
})
export class RedisModule {}
14 changes: 14 additions & 0 deletions BE/src/common/redis/redis.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Provider } from '@nestjs/common';
import Redis from 'ioredis';
import dotenv from 'dotenv';

dotenv.config();
export const RedisProvider: Provider = {
provide: 'REDIS_CLIENT',
useFactory: () => {
return new Redis({
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
});
},
};
1 change: 1 addition & 0 deletions BE/src/stock/list/interface/search-params.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export interface SearchParams {
name?: string;
market?: string;
code?: string;
userId: number;
}
9 changes: 7 additions & 2 deletions BE/src/stock/list/stock-list.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Controller, Get, Query } from '@nestjs/common';
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { JwtAuthGuard } from 'src/auth/jwt-auth-guard';
import { StockListService } from './stock-list.service';
import { StockListResponseDto } from './dto/stock-list-response.dto';

Expand Down Expand Up @@ -32,12 +34,15 @@ export class StockListController {
@ApiQuery({ name: 'market', required: false })
@ApiQuery({ name: 'code', required: false })
@Get('/search')
@UseGuards(JwtAuthGuard)
async searchWithQuery(
@Req() req: Request,
@Query('name') name?: string,
@Query('market') market?: string,
@Query('code') code?: string,
): Promise<StockListResponseDto[]> {
return this.stockListService.search({ name, market, code });
const userId = parseInt(req.user.userId, 10);
return this.stockListService.search({ name, market, code, userId });
}

@ApiOperation({
Expand Down
6 changes: 4 additions & 2 deletions BE/src/stock/list/stock-list.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RedisDomainService } from 'src/common/redis/redis.domain-service';
import { RedisModule } from 'src/common/redis/redis.module';
import { StockListRepository } from './stock-list.repostiory';
import { StockListService } from './stock-list.service';
import { StockListController } from './stock-list.controller';
import { Stocks } from './stock-list.entity';

@Module({
imports: [TypeOrmModule.forFeature([Stocks])],
imports: [TypeOrmModule.forFeature([Stocks]), RedisModule],
controllers: [StockListController],
providers: [StockListRepository, StockListService],
providers: [StockListRepository, StockListService, RedisDomainService],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 이거 RedisModule 임포트하면 providers에 RedisDomainService 추가 안해도 잘 동작하더라고요! providers에서는 빼도 될 것 같습니당

exports: [],
})
export class StockListModule {}
25 changes: 24 additions & 1 deletion BE/src/stock/list/stock-list.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { RedisDomainService } from 'src/common/redis/redis.domain-service';
import { StockListRepository } from './stock-list.repostiory';
import { Stocks } from './stock-list.entity';
import { StockListResponseDto } from './dto/stock-list-response.dto';
import { SearchParams } from './interface/search-params.interface';

@Injectable()
export class StockListService {
constructor(private readonly stockListRepository: StockListRepository) {}
private readonly SearchHistoryLimit = 10;

constructor(
private readonly stockListRepository: StockListRepository,
private readonly redisDomainService: RedisDomainService,
) {}

private toResponseDto(stock: Stocks): StockListResponseDto {
return new StockListResponseDto(stock.code, stock.name, stock.market);
Expand All @@ -27,7 +33,24 @@ export class StockListService {
}

async search(params: SearchParams): Promise<StockListResponseDto[]> {
await this.addSearchTermToRedis(params);
const stocks = await this.stockListRepository.search(params);
return stocks.map((stock) => this.toResponseDto(stock));
}

async addSearchTermToRedis(params: SearchParams) {
const key = `search:${params.userId}`;
const timeStamp = Date.now();

const { name, market, code } = params;

const searchTerm = name || market || code;

await this.redisDomainService.zadd(key, timeStamp, searchTerm);

const searchHistoryCount = await this.redisDomainService.zcard(key);
if (searchHistoryCount > this.SearchHistoryLimit) {
await this.redisDomainService.zremrangebyrank(key, 0, 0);
}
}
}
Loading