本章节内容: 1. 使用 Docker 运行 Minio 服务; 2. 设置 Minio; 3. 在 NestJS 中引入 Minio 服务与预签名接口。
本系列教程将教你使用 NestJS 构建一个生产级别的 REST API 风格的权限管理后台服务。
该教程主要包含以下内容:
- 用户登录,包含身份验证、无感刷新 token、单点登录;
- 用户、角色、权限的增删改查;
- 接口级别的权限控制,使用装饰器与守卫实现;
- 接口返回数据格式统一,使用拦截器与异常过滤器实现;
- 使用
Winston
进行日志管理; Minio
的使用,包含文件上传预签名等;- 编写
Swagger API
文档; - 数据库设计与
Prisma
建模 - 单元测试;
- 生产环境部署,使用
Docker
。
主要技术栈:NestJS、TS、PostgreSQL、Prisma、Redis、Minio、Winston、Docker。
代码仓库,在线预览地址,账号:test,密码:d.12345
1. 使用 Docker 运行 Minio 服务
打开 docker-compose.yml
文件,添加以下内容:
ts
...
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
volumes:
...
minio_data:
重新运行 docker-compose --env-file .env.development up --build
命令构建容器。
在浏览器中输入 http://localhost:9001/
网址,就可以看到 Minio 的管理界面了。
2. 设置 Minio
首先输入我们在 docker-compose.yml
中设置的账号密码,登录进入 Minio 的管理后台网站。
然后我们需要添加一个 Access Keys
,后续通过 NestJS
访问这个服务时需要。先点击左边菜单栏的 Access Keys
,再点击右边的 Create access key
按钮。 如图所示。
保存好 Access Key
与 Secret Key
,后续需要用到。
最后还需要调整一下桶的访问策略,按下图所示操作:
3. 在 NestJS 中引入 Minio 服务
3.1 添加环境变量配置
首先,在 .env.development
中添加相关环境变量配置:
env
# Minio
MINIO_END_POINT="localhost"
MINIO_PORT=9000
MINIO_USE_SSL=false
MINIO_ACCESS_KEY="..."
MINIO_SECRET_KEY="..."
MINIO_BUCKET_NAME="avatar"
MINIO_EXPIRES_IN=120
其次,在 /src/common/config/index.ts
中添加相应的常量:
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
模块。
然后安装 Minio 库,输入 pnpm i minio
。
最后,打开 /src/upload/upload.service.ts
文件添加以下代码:
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
文件添加一个预签名方法:
ts
async presignedUrl(fileName: string) {
const config = getBaseConfig(this.configService);
const url = await this.minioClient.presignedPutObject(
config.minio.bucketName,
fileName,
config.minio.expiresIn,
);
return { presignedUrl: url };
}
该方法接收一个文件名参数,返回一个上传文件链接。
其次,新建一个 /src/upload/dto/presigned.dto.ts
文件并添加以下内容:
ts
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class PresignedDto {
@IsString({ message: '文件名必须是字符串' })
@IsNotEmpty({ message: '文件名不能为空' })
@ApiProperty({ description: '文件名' })
filename: string;
}
该 DTO
将用来校验接口参数。
然后,新建一个 /src/upload/entities/presigned.entity.ts
文件并添加以下内容:
ts
import { ApiProperty } from '@nestjs/swagger';
export class PresignedEntity {
@ApiProperty({
description: '预签名 URL',
})
presignedUrl: string;
}
该实体将用作接口返回对象。
最后,修改 /src/upload/upload.controller.ts
文件的内容为:
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';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@ApiOperation({ summary: '获取预签名 URL' })
@ApiBaseResponse(PresignedEntity)
@Post('presigned')
presignedUrl(@Body() presignedDto: PresignedDto): Promise<PresignedEntity> {
return this.uploadService.presignedUrl(presignedDto.filename);
}
}
至此,文件预签名接口就开发完成了。运行开发环境后,可以用 postman 之类的工具测试该接口是否能返回预签名 URL,然后再测试能否通过预签名 URL 将图片文件上传到 minio。
接口的单元测试代码请查看仓库。