Nestjs+minio 上传下载删除文件

前言

基于上一遍讲了如何利用nestjs 搭建一个restful风格的后端,现在接着讲如何结合minio实现文件的上传和下载。
文中涉及到的minio 知识点参考https://blog.csdn.net/zw52yany/article/details/101217708这遍文章

完整代码请看 https://github.com/zw-slime/book-be

上传

1. 创建user和picture 实体以及关联关系

user和picture是一对多的关系,利用TypeORM的ManyToOne JoinColumn 建立两个变直接的映射关系

@Entity()
export class PictureEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(type => UserEntity, user => user.pictures)
  @JoinColumn()
  owner: UserEntity;

  @Column({
    nullable: false,
  })
  bucketName: string;

  @Column({
    nullable: false,
    unique: true,
  })
  fileName: string;

  @Column({
    nullable: false,
  })
  fileType: string;

  @Column({
    default: '',
  })
  originName: string;

  @Column({
    default: false,
  })
  isPublic: boolean;
}

@Entity({ name: 'user' })
export class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    type: 'varchar',
    length: 255,
    unique: true,
  })
  name: string;

  @Column({
    type: 'varchar',
    length: 255,
  })
  password: string;

  @Column({
    type: 'varchar',
    length: 255,
  })
  email: string;

  @Column({
    type: 'varchar',
    length: 255,
  })
  salt: string;

  @Column({
    name: 'create_at',
    type: 'timestamp',
    onUpdate: 'CURRENT_TIMESTAMP',
  })
  createAt: Date;

  @OneToMany(type => PictureEntity, picture => picture.owner)
  pictures: PictureEntity[];
}

具体形成的数据库表如下:
Nestjs+minio 上传下载删除文件_第1张图片
在这里插入图片描述

2. controller定义api

(1)FileInterceptor绑定上传的文件字段,然后通过@UploadedFile() file 可以获得file
(2)利用hash 生成文件名,在利用minio提供的sdk putObject 上传到minio服务器上
(3)最后利用typeOrm提供的方法上传到数据库
controller的代码如下:

@Post('upload-file')
  @UseInterceptors(FileInterceptor('file'))
  async uploadMinio(@UploadedFile() file, @Req() req) {
    const temp = req.header('authorization').split(' ');
    const userId = (this.authService.decode(temp[temp.length - 1]) as any)
      .userId;

    const fileHash = hash({
      name: file.originalname,
      time: new Date().getTime(),
    });

    await this.minioService.uploadFile(fileHash, file.buffer, file.mimetype);  

    await this.pictureSerive.addPicture({
      userId,
      fileName: fileHash,
      fileType: file.mimetype,
      originName: file.originalname,
    });

    return { fileName: fileHash };
  }

minioService 上传文件的代码如下: 先判断bucker存不存在,不存在就新建

async uploadFile(fileName, file: Buffer, type) {
    this.initial();
    if (!this.minioClient.bucketExists(BucketName)) {
      await this.createBucket();
    }
    const metaData = {
      'Content-Type': type,
    };
    try {
      return await this.minioClient.putObject(
        BucketName,
        fileName,
        file,
        1024,
        metaData,
      );
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }

pictureService的保存文件到数据库的代码如下:这里存的是一条记录,并不是真实的文件,而是文件对应的minio object name,同时通过owner关联user 表

async addPicture(params: AddPictureParams) {
    try {
      return await this.pictureRepository.save({
        owner: {
          id: params.userId,
        },
        fileName: params.fileName,
        bucketName: BucketName,
        fileType: params.fileType,
        originName: params.originName,
      });
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }

最后 测试得到一条数据库记录为:
Nestjs+minio 上传下载删除文件_第2张图片
在这里插入图片描述

下载

1. 下载的controller

(1)先判断用户对图片有没有权限,有权限就返回获得的数据,没权限就抛异常
(2)再通过minio sdk提供的方法获得文件流
(3)写入文件流,写入完成response返回下载文件流

@Get('/:id')
  async get(@Param('id') id: string, @Res() res: Response, @Req() req) {
    // 查询pic数据库判断用户对图片有没有权限
    const pic = await this.pictureSerive.canReadPicture(
      id,
      req.header('authorization'),
    );

    // 获取文件后缀名
    let ext = '';
    if (pic[0].originName.indexOf('.') > -1) {
      ext = pic[0].originName.split('.')[
        pic[0].originName.split('.').length - 1
      ];
    }

    // 返回文件下载流
    const readerStream = await this.minioService.getObject(id);

    readerStream.on('data', chunk => {
      res.write(chunk, 'binary');
    });
    res.set({
      'Content-Type': 'application/octet-stream',
      'Content-Disposition': 'attachment; filename=' + id + '.' + ext,
    });
    readerStream.on('end', () => {
      res.end();
    });
    readerStream.on('error', err => {
      console.log(err.stack);
      throw new BadRequestException(err.stack);
    });
  }
2.下面是minioservice 获取object的代码
  async getObject(fileName) {
    this.initial();
    try {
      return await this.minioClient.getObject(BucketName, fileName);
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }
2.判断用户对数据库有没有权限的代码

这里涉及到跨表的关联查询,实现方法: find({relations: [‘实体里面定义的关联字段’]}),具体代码如下

 // 查询pic数据库判断用户对图片有没有权限
  canReadPicture(fileName: string, authorization: string) {
    return new Promise(async (resolve, reject) => {
      const temp = authorization.split(' ');
      const userId = (this.authService.decode(temp[temp.length - 1]) as any)
        .userId;

      const pic = await this.findPictureByFileName(fileName);

      if (!pic || pic.length <= 0) {
        return reject(new BadRequestException('该文件不存在'));
      }
      if (pic[0].owner.id !== userId && !pic[0].isPublic) {
        return reject(new NOPicturePermissionException());
      }
      return resolve(pic);
    });
  }
  
async findPictureByFileName(fileName: string) {
    try {
      return await this.pictureRepository.find({
        where: { fileName },
        relations: ['owner'],
      });
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }

最后测试得到的 http://localhost:3000/picture/d6a5f9e569f2875bee9322f10a0d21eb0421d760 会下载文件

删除

1.删除的controller

(1)同上判断对文件有木有权限
(2)删除数据库记录
(3)删除minio文件

 @Post('delete')
  async delete(@Body() param: DeletePictureValidator, @Req() req) {
    // 查询pic数据库判断用户对图片有没有权限
    const pic = await this.pictureSerive.canReadPicture(
      param.fileName,
      req.header('authorization'),
    );
    await this.pictureSerive.deletePictureByFileName(param.fileName);

    await this.minioService.deleteObject(param.fileName);
    return { fileName: param.fileName };
  }
2. 删除数据库记录
  async deletePictureByFileName(fileName: string) {
    try {
      return await this.pictureRepository.delete({ fileName });
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }
3. 删除minio文件
async deleteObject(fileName) {
    this.initial();
    try {
      return await this.minioClient.removeObject(BucketName, fileName);
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }

获取文件列表

1.controller
 @Get('list')
  async findAllPicture(@Req() req) {
    return await this.pictureSerive.findMyPictureList(
      req.header('authorization'),
    );
  }
2. service
// 根据userId查询picture list
  async findMyPictureList(authorization: string) {
    const temp = authorization.split(' ');
    const userId = (this.authService.decode(temp[temp.length - 1]) as any)
      .userId;

    try {
      return await this.pictureRepository.find({ owner: { id: userId } });
    } catch (e) {
      throw new BadRequestException(e.message);
    }
  }

最后得到的数据结果:
Nestjs+minio 上传下载删除文件_第3张图片

你可能感兴趣的:(NestJS)