Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- AWS
- Dinosaur
- Python
- react
- MongoDB
- GIT
- mongoose
- Bull
- 게임
- nodejs
- OCR
- nestjs
- 자료구조
- class
- Queue
- cookie
- JavaScript
- 공룡게임
- jest
- typeORM
- game
- Nest.js
- TypeScript
- MySQL
- Sequelize
- flask
- Express
- 정렬
- dfs
Archives
- Today
- Total
포시코딩
[NestJS] How can I respond with a file and delete with interceptor 본문
728x90
이번 포스팅에선
1. DB에서 typeorm을 통해 데이터 stream 받기
2. 데이터 엑셀 파일로 임시 저장 (위 링크 참고)
3. 엑셀 파일 요청한 클라이언트로 전달
4. 전달 후 interceptor를 통해 서버에 저장된 임시 엑셀 파일 제거
위 네 가지 과정에 대해 알아보겠다.
excel.service
import { Injectable } from '@nestjs/common';
import * as XLSX from 'xlsx';
import { join } from 'path';
import * as fs from 'fs';
@Injectable()
export class ExcelService {
async createExcelFile(prefix: string, data: Array<string | Buffer>) {
// 파일 작명
const currentDate = new Date();
const formattedDate = currentDate.toISOString().slice(0, 10).replace(/-/g, '');
const filename = `${prefix}_${formattedDate}.xlsx`;
// temp 임시 폴더 없다면 생성, 있다면 무시
fs.mkdirSync(join(process.cwd(), `temp`), { recursive: true });
const filePath = join(process.cwd(), `temp/${filename}`);
// filePath 위치에 엑셀 다운로드
const wb = XLSX.utils.book_new();
const newWorksheet = XLSX.utils.json_to_sheet(data);
XLSX.utils.book_append_sheet(wb, newWorksheet, 'Sheet1');
const wbOptions: any = { bookType: 'xlsx', type: 'binary' };
XLSX.writeFile(wb, filePath, wbOptions);
return { filename, filePath };
}
}
target.service
async getTargetsByExcel() {
// typeorm stream()을 통해 데이터 받기
const queryStream = await this.targetRepository
.createQueryBuilder('t')
.select(*)
.stream();
let data: Array<string | Buffer> = [];
await new Promise<void>((resolve, reject) => {
queryStream.on('data', (chunk) => {
// stream을 통해 받는 데이터 data array에 밀어넣기
data.push(chunk);
});
queryStream.on('end', () => {
// stream 종료 시 resolve()
resolve();
});
queryStream.on('error', (err) => {
reject(err);
});
});
return this.excelService.createExcelFile('target', data);
}
위 코드에선 stream으로 받는 데이터가 모두 받아진 후 엑셀로 만들기 때문에 굳이 stream을 쓸 필요가 없다고 생각한다.
target.controller
@Get('excel')
@UseInterceptors(ExcelFileCleanupInterceptor)
async getTargetsByExcel(@Res() response: Response) {
const { filename, filePath } = await this.targetService.getTargetsByExcel();
const file = fs.createReadStream(filePath);
// 파일 전달 후 interceptor에서 서버에 저장된 임시 엑셀 파일을 지우기 위해 filePath를 response.locals에 저장
response.locals.filePathToDelete = filePath;
// setHeader에서 세팅하는 filename이 client에서 다운로드 했을 때의 파일명이 된다.
response.setHeader(
'Content-Disposition',
`attachment; filename=${filename}`,
);
return file.pipe(response);
}
excel.interceptor
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
import * as fs from 'fs';
@Injectable()
export class ExcelFileCleanupInterceptor implements NestInterceptor {
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
// nestjs의 생명 주기에서 request가 아닌 response에 대해서만 동작하게
const response = context.switchToHttp().getResponse();
const responseFinished = new Promise<void>((resolve) => {
response.once('finish', resolve);
});
return next.handle().pipe(
tap(async () => {
// 위 responseFinished를 통해 controller의 return이 된 후 동작하게 만든다.
await responseFinished;
const filePath = response.locals?.filePathToDelete;
if (filePath) {
try {
// fs.unlinkSync를 통해 해당 위치의 파일 제거
fs.unlinkSync(filePath);
} catch (err) {
console.error('Error deleting file:', err);
}
}
}),
);
}
}
주석으로 설명 다 달아놓음
728x90
'JavaScript' 카테고리의 다른 글
Onclick vs. AddEventListener, 버블링, 캡쳐링 (0) | 2023.07.17 |
---|---|
OpenCV.js - CV(Computer Vision) 오픈 소스 라이브러리 (0) | 2023.07.09 |
Tesseract.js - (OCR) 이미지 텍스트 인식 라이브러리 (0) | 2023.07.09 |
Cropper.js - 이미지 자르기 라이브러리 (0) | 2023.07.09 |
[Vue 3.0] data()와 methods() 대신 setup() 사용하기 (0) | 2023.06.21 |