NestJS 项目实战-权限管理系统开发(六)

427 阅读4分钟

本系列教程将教你使用 NestJS 构建一个生产级别的 REST API 风格的权限管理后台服务【代码仓库地址】。

在线预览地址】账号:test,密码:d.12345

本章节内容: 1. 使用 Docker 运行 Minio 服务; 2. 设置 Minio; 3. 在 NestJS 中引入 Minio 服务与预签名接口。

1. 使用 Docker 运行 Minio 服务

打开 docker-compose.yml 文件,添加以下内容:

...

minio:
    image: bitnami/minio:latest
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - minio_data:/data
    environment:
      MINIO_ROOT_USER: wansongtao # 管理后台登录用户账号
      MINIO_ROOT_PASSWORD: w.12345.st # 管理后台登录用户密码
      MINIO_DEFAULT_BUCKETS: avatar # 新建一个桶,名称为 avatar
      
volumes:
  ...
  minio_data:

重新运行 docker-compose --env-file .env.development up --build 命令构建容器。

然后,在浏览器中打开 http://localhost:9001/ 就可以看到 Minio 的管理界面了。

image.png

2. 设置 Minio

首先输入我们在 docker-compose.yml 中设置的账号密码,登录进入 Minio 的管理后台网站。

然后我们需要添加一个 Access Keys ,后续通过 NestJS 访问这个服务时需要。先点击左边菜单栏的 Access Keys,再点击右边的 Create access key 按钮。 如图所示。

image.png 保存好 Access KeySecret Key,后续需要用到。 image.png

最后还需要调整一下桶的访问策略,按下图所示操作:

image.png image.png image.png

3. 在 NestJS 中引入 Minio 服务

3.1 添加环境变量配置

首先,在 .env.development 中添加相关环境变量配置:

# Minio
MINIO_END_POINT="localhost"
MINIO_PORT=9000
MINIO_USE_SSL=false
# 填入之前在 Minio 后台创建的 key
MINIO_ACCESS_KEY="..."
MINIO_SECRET_KEY="..."
MINIO_BUCKET_NAME="avatar"
MINIO_EXPIRES_IN=120

其次,在 /src/common/config/index.ts 中添加相应的常量:

  minio: {
    endPoint: configService.get<string>('MINIO_END_POINT'),
    port: +configService.get<number>('MINIO_PORT'),
    useSSL: configService.get('MINIO_USE_SSL') === 'true',
    accessKey: configService.get<string>('MINIO_ACCESS_KEY'),
    secretKey: configService.get<string>('MINIO_SECRET_KEY'),
    bucketName: configService.get<string>('MINIO_BUCKET_NAME'),
    expiresIn: +configService.get<number>('MINIO_EXPIRES_IN'),
  },

3.2 新建 Upload 模块

接下来在项目根目录新开一个终端窗口,输入 nest g res upload 命令,新建一个 upload 模块。

image.png

然后安装 Minio 库,输入 pnpm i minio

最后,打开 /src/upload/upload.service.ts 文件添加以下代码:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { getBaseConfig } from 'src/common/config';
import { Client } from 'minio';

@Injectable()
export class UploadService {
  private minioClient: Client;

  constructor(private readonly configService: ConfigService) {
    const config = getBaseConfig(configService);

    this.minioClient = new Client({
      endPoint: config.minio.endPoint,
      port: config.minio.port,
      useSSL: config.minio.useSSL,
      accessKey: config.minio.accessKey,
      secretKey: config.minio.secretKey,
    });
  }
}

启动开发环境,没有报错则代表连上 Minio 服务了。

注意:要先运行相关 docker 服务哦。

3.3 添加预签名接口

首先,打开 /src/upload/upload.service.ts 文件添加一个预签名方法:

  async presigned(fileName: string) {
    const config = getBaseConfig(this.configService);

    const url = await this.minioClient.presignedPutObject(
      config.minio.bucketName,
      fileName,
      config.minio.expiresIn,
    );

    return { presignedUrl: url };
  }

使用 minio 提供的 presignedPutObject 预签名方法,传入桶名、文件名与过期时间即可生成一个上传链接。

其次,新建一个 /src/upload/dto/presigned.dto.ts 文件并添加以下内容:

import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';

export class PresignedDto {
  @IsString({ message: '文件名必须是字符串' })
  @IsNotEmpty({ message: '文件名不能为空' })
  @ApiProperty({ description: '文件名' })
  filename: string;
}

DTO 将用来校验接口参数与用作 Swagger 文档中该接口参数的类型说明。

然后,新建一个 /src/upload/entities/presigned.entity.ts 文件并添加以下内容:

import { ApiProperty } from '@nestjs/swagger';

export class PresignedEntity {
  @ApiProperty({
    description: '预签名 URL',
  })
  presignedUrl: string;
}

该实体将用作接口返回对象与 Swagger 文档中该接口响应数据的类型说明。

最后,修改 /src/upload/upload.controller.ts 文件的内容为:

import { Body, Controller, Post } from '@nestjs/common';
import { UploadService } from './upload.service';
import { ApiOperation } from '@nestjs/swagger';
import { ApiBaseResponse } from 'src/common/decorator/api-base-response.decorator';
import { PresignedDto } from './dto/presigned.dto';
import { PresignedEntity } from './entities/presigned.entity';

@ApiTags('upload')
@Controller('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @ApiOperation({ summary: '获取预签名 URL' })
  @ApiBaseResponse(PresignedEntity)
  @Post('presigned')
  presigned(@Body() presignedDto: PresignedDto): Promise<PresignedEntity> {
    return this.uploadService.presigned(presignedDto.filename);
  }
}

@ApiTags('upload') 装饰器用来给整个控制器添加 upload 标签,方便对 Swagger API 文档中的接口进行分组管理。

@ApiOperation() 装饰器用来为 Swagger API 文档中该接口添加说明信息。

@ApiBaseResponse() 是我们之前封装的一个装饰器,是用来:

  1. 定义 API 端点的成功响应;
  2. 为 Swagger API 文档提供该接口响应数据的类型说明。

@Post('presigned') 装饰器用来声明 POST 类型的接口,这里还设置了该接口的地址为 presigned

@Body() 用来获取接口请求体中的参数数据。 presignedDto: PresignedDto 表示指定使用 PresignedDto 对象校验参数格式。

至此,文件预签名接口就开发完成了。运行开发环境后,可以用 postman 之类的工具测试该接口是否能返回预签名 URL,然后再测试能否通过预签名 URL 将图片文件上传到 minio。

接口的单元测试代码请查看仓库,下一章节见~