* 개념
- res, req에 둘다 실행되는 특징이 있다.
- 로거, @Transactinoal 등에 유용하다.
- res, req에 모두 반복되는 공통 로직을 처리하고싶을때 사용하면 좋다.
* 로거구현
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, map, tap } from "rxjs";
import {v4 as uuid} from 'uuid';
@Injectable()
export class LogInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
/**
* 요청이 들어올때 REQ 요청이 들어온 타임스탬프를 찍는다.
* [REQ] {uuid} {요청 path} {요청 시간}
*
* 요청이 끝날때 (응답이 나갈때) 다시 타임스탬프를 찍는다.
* [RES] {uuid} {요청 path} {응답 시간} {얼마나 걸렸는지 ms}
*/
const now = new Date();
const uid = uuid();
const req = context.switchToHttp().getRequest();
// /posts
// /common/image
const path = req.originalUrl;
// [REQ] {요청 path} {요청 시간}
console.log(`[REQ] [UUID: ${uid} ] ${path} ${now.toLocaleString('kr')}`)
// return next.handle()을 실행하는 순간
// 라우트의 로직이 전부 실행되고 응답이 반환된다.
// observable로
return next
.handle()
.pipe(
tap(
// [RES] {요청 path} {응답 시간} {얼마나 걸렸는지 ms}
(observable)=>
console.log(`[RES] [UUID: ${uid} ] ${path} ${new Date().toLocaleString('kr')}
${new Date().getMilliseconds() - now.getMilliseconds()}ms`),
),
);
}
}
app.useGlobalInterceptors(new LogInterceptor());
* @Transactional 구현
- 문제
- 트랜잭션 할때마다 반복되는 코드들..
- 해결 : interceptor로 반복되는 로직을 처리해버리자
import {
CallHandler,
ExecutionContext,
Injectable,
InternalServerErrorException,
NestInterceptor
} from "@nestjs/common";
import { catchError, Observable, tap } from "rxjs";
import { DataSource } from "typeorm";
/**
* req에 qr을 넣어주기
* res가 controller에 들어가기전에 startTransaction
* 끝난후 commit, rollback
*/
@Injectable()
export class TransactionInterceptor implements NestInterceptor {
constructor(
private readonly dataSource : DataSource,
) {
}
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
const req = context.switchToHttp().getRequest();
const qr = this.dataSource.createQueryRunner();
await qr.connect();
//이 시점에서 같은 qr을 사용해야 트랜잭션이 묶인다!! -> 즉, qr을 인자로 전달해야한다.
await qr.startTransaction();
/**
* next.handle후에는 로직을 마친 res가 올때 실행되는 코드.
*/
return next
.handle()
.pipe(
catchError(async (e)=> {
await qr.rollbackTransaction();
await qr.release();
throw new InternalServerErrorException(e.message);
}
),
tap( async() => {
await qr.commitTransaction();
await qr.release();
}
),
);
}
}
- 문제2 : qr을 어케전달?
- 해결 : req에 넣으면됨 ㅋㅋ
req.qr = qr;
- 유능한 개발자의 구현 : 데코레이터로 만들어서 transactional interceptor와 같이 사용되는지 확인후 qr을 반환하도록 만듬.
import { createParamDecorator, ExecutionContext, InternalServerErrorException } from "@nestjs/common";
/**
* req에 넣어준 qr을 검사하고
* qr을 리턴해주는 데코레이터
*
*/
export const QueryRunner = createParamDecorator((data, context : ExecutionContext) => {
const req = context.switchToHttp().getRequest();
//FE와 BE의 평화가 찾아옴.
if (!req.qr) {
throw new InternalServerErrorException('' +
'QueryRunner 데코레이저는 Transactional Interceptor 와 함께 사용해야합나다. ');
}
return req.qr;
});
- 완성된 컨트롤러 코드
- try-catch문도 필요없음.
- 인터셉터에서 에러잡아주니까.
- 비즈니스로직 에만 집중 가능.
@Post()
@UseGuards(AccessTokenGuard)
@UseInterceptors(TransactionInterceptor)
async postPost(
@User('id') userId : number,
@Body() body : CreatePostDto,
@QueryRunner() qr: QR, //req에서 qr만 파싱해옴.
){
//로직실행
const post = await this.postsService.createPost(userId ,body, qr);
/**
* body의 image 이름들에대해
* 각각 정보들을 넣어서 이미지를 만듬(db에 저장)
* 주인은 post
*/
for(let i=0;i<body.images.length;i++){
await this.postsImagesService.createPostImage({
post,
order:i,
path:body.images[i],
type:ImageModelType.POST_IMAGE,
}, qr);
}
return this.postsService.getPostById(post.id, qr);
}
'JS > Nest.js' 카테고리의 다른 글
[Nest] Exception Filter 구현 (0) | 2024.09.22 |
---|---|
[Nest] Transaction 구현 (0) | 2024.09.22 |
[Next] 연관관계 매핑, ImageModel 생성 구현 (0) | 2024.09.22 |
[Nest] 이미지 업로드 구현V2 (0) | 2024.09.22 |
[Nest] 이미지 업로드 구현 V1 (0) | 2024.09.22 |