파일을 업로드할때 파일의 크기에 따라 처리시간이 오래걸릴 수 있다. 요즘 시대가 어떤 시대인가? 8282 대한민국 사람이라면 불편할 수 있다.
그래서 고안된 방법이, 파일을 선업로드하는 것이다. 사용자가 업로드 버튼을 누르기 전 단계, 즉 사용자가 파일을 선택한 순간 업로드를 시키는 것이다. 물론 장단점은 있다.
사용자가 선택했지만 업로드하지 않았을때와 같은 경우... 리소스 낭비가 될 수 있다.
[1] 선업로드: temp 폴더에 임시로 이미지 파일을 저장
위 코드는 이전 게시글에 있는 코드를 그대로 가져왔다. 이 부분이 바로 선업로드로 사용될 부분.
MulterModule 에 register 메서드의 인자중 storage 가 바로 사용자가 이미지를 최종 업로드하기 전 바로 선택한 단계이고, 이 단계에서 임시로 temp 폴더에 이미지를 저장시켜준다.
import { BadRequestException, Module } from '@nestjs/common';
import { CommonService } from './common.service';
import { CommonController } from './common.controller';
import { MulterModule } from '@nestjs/platform-express';
import { extname } from 'path';
import * as multer from 'multer'
import { TEMP_FOLDER_PATH } from 'src/common/const/path.const';
import { v4 as uuid } from 'uuid';
@Module({
imports: [
MulterModule.register({
limits: {
fileSize: 10000000 // bytes
},
fileFilter: (req, file, callBack) => {
const ext = extname(file.originalname)
if(ext !== '.jpg' && ext !== '.jpeg' && ext !== '.png') {
return callBack(new BadRequestException('jpg/jpeg/png 파일만 업로드 가능합니다.'), false);
};
return callBack(null, true)
},
storage: multer.diskStorage({
destination: (req, file, callBack) => {
callBack(null, TEMP_FOLDER_PATH)
},
filename: (req, file, callBack) => {
callBack(null, `${uuid()}${extname(file.originalname)}`); // 123123-123123-123123-123123.jpg
}
})
})
],
controllers: [CommonController],
providers: [CommonService],
exports: [CommonService]
})
export class CommonModule {}
import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { CommonService } from './common.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { AccessTokenGuard } from 'src/auth/guard/bearer-token.guard';
@Controller('common')
export class CommonController {
constructor(private readonly commonService: CommonService) {}
@Post('image')
@UseInterceptors(FileInterceptor('image'))
@UseGuards(AccessTokenGuard)
postImage(
@UploadedFile() file: Express.Multer.File
){
return {
fileName: file.filename
};
}
}
[2] 최종 업로드
사용자가 최종적으로 업로드 버튼을 눌렀을때 선택한 이미지, temp 폴더에 있는 이미지가 posts 폴더로 이동되면서 image_model 리파지토리에 저장되고 동시에 posts_model 리파지토리에도 저장된다.
import { Controller, Post, Body, UploadedFile, } from '@nestjs/common';
import { User } from 'src/users/decorator/user.decorator';
import { PostsService } from './posts.service';
@Controller('posts')
export class CommonController {
constructor(private readonly postsService: PostsService) {}
@Post()
@UseGuards(AccessTokenGuard)
postImage(
@User('id') userId: number, // 커스텀 데코레이터, ExecutionContext 에서 Request 에 있는 유저객체 가져옴
@Body() body: CreatePostDto
){
// posts_model 리포지토리에 저장
const post = this.postsService.createPost(userId, body);
// image_model 리포지토리에 저장
// temp 폴더에서 posts 폴더로 이동
for(let i = 0; i < body.images.length; i++){
await this.postsService.createPostImage({
post,
order: i,
path: body.images[i],
type: ImageModelType.POST_IMAGE // enum 이고 0 이다.
});
};
return this.postsService.getPostById(post.id);
}
}
// service.ts
import { basename, join } from 'path';
import { promises } from 'fs'; // File System
import { PostsModel } from "src/posts/entity/posts.entity";
import { ImageModel } from "src/image/entity/image.entity";
constructor(){
@InjectRepository(PostsModel)
private readonly postRepository: Repository<PostsModel>,
@InjectRepository(ImageModel)
private readonly imageRepository: Repository<ImageModel>
}
async createPostImage(dto: CreatePostDto){
// dto의 이미지 이름을 기반으로
// 파일의 경로를 생성한다.
const tempFilePath = join(
TEMP_FOLDER_PATH,
dto.image,
);
try{
await promises.access(tempFilePath); // 파일이 존재하는지 확인, 만약에 존재하지 않는다면 에
}catch(e){
throw new BadRequestException('존재하지 않는 파일입니다.');
};
const fileName = basename(tempFilePath); // 파일의 이름만 가져오기, 예를들어, /a/b/c.jpg 라면 c.jpg 만 가져온다.
// image 리파지토리에 저장
const result = await this.imageRepository.save({
...dto,
});
// {프로젝트 경로}/public/posts/c.jpg
const newPath = join(
POST_IMAGE_PATH,
fileName
);
await promises.rename(tempFilePath, newPath); // temp 에서 posts 로 파일 이동
}
createPost(authorId: number, postDto: CreatePostDto){
const post = this.postRepository.create({
author: {
id: authorId
},
...postDto,
images: [],
likeCount: 0,
commentCount: 0
});
return this.postRepository.save(post);
}
posts_model 은 image_model 과 relation 이 맺어져있다. 외래키(foreign key)는 image_model 쪽에 있고, posts_model 을 SELECT 할때 relation 옵션을 추가하면 두 리파지토리에 있는 정보를 연결지어 같이 볼 수 있다.
{
"id": 114,
"updatedAt": "2024-06-24T22:42:04.134Z",
"createdAt": "2024-06-24T22:42:04.134Z",
"title": "제목",
"content": "내용",
"likeCount": 0,
"commentCount": 0,
"author": {
"id": 2,
"updatedAt": "2024-06-24T16:27:32.564Z",
"createdAt": "2024-06-24T16:27:32.564Z",
"nickname": "chanchan1",
"email": "chanchan1@test.com",
"password": "$2b$10$GOBnuLWuF2exQmoJtFYLie6Hc8BkHL7wYOZKyMeQAcmsVbiNhSQge",
"role": "user"
},
"images": [
{
"id": 9,
"updatedAt": "2024-06-24T22:42:04.134Z",
"createdAt": "2024-06-24T22:42:04.134Z",
"order": 0,
"type": 0,
"path": "C:\\Users\\farmc\\OneDrive\\바탕 화면\\workspace\\NestJS\\cf_sns\\public\\posts\\a1c216c3-3ba6-4a2c-970e-9e6deb533113.jpg"
}
]
}
'Backend > NestJS' 카테고리의 다른 글
main.ts 파해치기 (useGlobalPipes 옵션 whitelist) (0) | 2024.06.28 |
---|---|
Transaction, 한 가지 이상의 CRUD 를 실행할 때 roll back (0) | 2024.06.27 |
이미지 파일 업로드 (0) | 2024.06.25 |
NestJS에서 validation 관련 설정 방법 (class-validator & class-transformer) (0) | 2024.06.18 |
[NestJs] 끄적끄적 (0) | 2023.03.29 |
댓글