NestJS 搭建博客系统(十)— 图床模块
前言
这里做图床模块主要为了节省资源,同时方便图片复用。 基本功能有图片列表和图片新增,上传图片。为了资源复用,可以在上传图片时计算图片 MD5 值对比数据库是否存在,图片模块需要保存图片路径和 md5 值。
开发图床模块
nest g mo modules/picture
nest g co modules/picture
nest g s modules/picture
Entity DTO VO
// src/modules/picture/entity/picture.entity.ts
import { Common } from 'src/common/entity/common.entity';
import { Entity, Column } from 'typeorm';
@Entity()
export class Picture extends Common{
// 图片路径
@Column('text')
src: string;
// 文件签名
@Column('text')
sign: string;
}
// src/modules/picture/dto/picture.dto.ts
import { IsNotEmpty } from "class-validator";
export class PictureDTO {
/**
* 图片路径
* @example /upload/static/1.png
*/
@IsNotEmpty({ message: '请输入图片路径' })
readonly src: string;
}
// src/modules/picture/dto/picture-create.dto.ts
import { PictureDTO } from "./picture.dto";
export class PictureCreateDto extends PictureDTO {
/**
* 图片md5
* @example asdfghjkl
*/
readonly sign?: string;
}
// src/modules/picture/vo/picture-info.dto.ts
import { SuccessVO } from "src/common/dto/success.dto";
import { PictureDTO } from "../dto/picture.dto";
export class PictureInfoItem extends PictureDTO{}
export class PictureInfoVO {
info: PictureInfoItem
}
export class PictureInfoSuccessVO extends SuccessVO {
data: {
info: PictureInfoItem
}
}
// src/modules/picture/vo/picture-list.dto.ts
import { PaginationDTO } from "src/common/dto/pagination.dto";
import { SuccessVO } from "src/common/dto/success.dto";
import { PictureDTO } from "../dto/picture.dto";
export class PictureListItem extends PictureDTO {}
export class PictureListVO {
list: PictureListItem[]
pagination: PaginationDTO
}
export class PictureListSuccessVO extends SuccessVO {
data: {
list: PictureListItem[]
pagination: PaginationDTO
}
}
引用 entity
// src/modules/picture/picture.modules.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Picture } from './entity/picture.entity';
import { PictureController } from './picture.controller';
import { PictureService } from './picture.service';
@Module({
imports: [
TypeOrmModule.forFeature([Picture]),
],
controllers: [PictureController],
providers: [PictureService]
})
export class PictureModule {}
控制器
import { Controller, Get, Post, Query, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { PageDTO } from 'src/common/dto/Page.dto';
import { PictureService } from './picture.service';
import { PictureInfoSuccessVO, PictureInfoVO } from './vo/picture-info.vo';
import { PictureListSuccessVO, PictureListVO } from './vo/picture-list.vo';
@ApiTags('图床模块')
@Controller('picture')
export class PictureController {
constructor(
private pictureService: PictureService
) {}
@ApiOkResponse({ description: '图片列表', type: PictureListSuccessVO })
@Get('list')
async getMany(
@Query() pageDto: PageDTO
): Promise<PictureListVO> {
return await this.pictureService.getMany(pageDto)
}
@ApiOkResponse({ description: '上传图片', type: PictureInfoSuccessVO })
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async upload(
@UploadedFile() file:any
): Promise<PictureInfoVO> {
console.log('controller', {file})
return await this.pictureService.upload(file)
}
}
Service
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { PageDTO } from 'src/common/dto/Page.dto';
import { getPagination } from 'src/utils/index.util';
import { Repository } from 'typeorm';
import { PictureCreateDto } from './dto/picture-create';
import { Picture } from './entity/picture.entity';
import { PictureInfoVO } from './vo/picture-info.vo';
import * as fs from 'fs';
import { encryptFileMD5 } from 'src/utils/cryptogram.util';
import { uploadStaticSrc } from 'src/config/upload/upload.config';
@Injectable()
export class PictureService {
constructor(
@InjectRepository(Picture)
private readonly pictureRepository: Repository<Picture>,
) {}
async getMany(
pageDto: PageDTO
) {
const { page, pageSize } = pageDto
const getList = this.pictureRepository
.createQueryBuilder('picture')
.select([
'picture.src',
])
.skip((page - 1) * pageSize)
.take(pageSize)
.getManyAndCount()
const [list, total] = await getList
const pagination = getPagination(total, pageSize, page)
return {
list,
pagination,
}
}
async create(
pictureCreateDTO: PictureCreateDto
): Promise<PictureInfoVO> {
const picture = new Picture()
picture.src = pictureCreateDTO.src
picture.sign = pictureCreateDTO.sign
const result = await this.pictureRepository.save(picture)
return {
info: result
}
}
async getOneBySign(sign: string) {
return await this.pictureRepository
.createQueryBuilder('picture')
.where('picture.sign = :sign', { sign })
.getOne()
}
async upload(file: any) {
const { buffer } = file
const currentSign = encryptFileMD5(buffer)
const hasPicture = await this.getOneBySign(currentSign)
if (hasPicture) {
return {
info: {
src: hasPicture.src,
isHas: true,
}
}
}
const arr = file.originalname.split('.')
const fileType = arr[arr.length - 1]
const fileName = currentSign + '.' + fileType
fs.writeFileSync(`./upload/${fileName}`, buffer)
const src = uploadStaticSrc + fileName
this.create({ src, sign: currentSign })
return {
info: {
src,
isHas: false
}
}
}
}
其中需要设置静态文件路径
// src/config/upload/upload.config.ts
// 静态文件路径 localhost/static/upload/xxx.jpg
export const uploadStaticSrc = '/static/upload/'
// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { TransformInterceptor } from './interceptor/transform.interceptor';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { uploadStaticSrc } from './config/upload/upload.config';
import { join } from 'path';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useGlobalPipes(new ValidationPipe())
app.useGlobalInterceptors(new TransformInterceptor())
app.useGlobalFilters(new HttpExceptionFilter())
app.useStaticAssets(join(__dirname, '..', 'upload'), {
prefix: uploadStaticSrc,
});
const options = new DocumentBuilder()
.setTitle('blog-serve')
.setDescription('接口文档')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('swagger-doc', app, document);
await app.listen(3000);
}
bootstrap();
至此,具有查重功能的图床模块已完成,虽然仅有列表查看功能和上传功能,但足以方便重复利用资源