美文网首页程序员
nest mysql 实战

nest mysql 实战

作者: baiqingchun | 来源:发表于2020-07-07 15:42 被阅读0次

    写在前面

    Nest(NestJS)是用于构建高效,可扩展的Node.js服务器端应用程序的框架。它使用渐进式JavaScript,并已构建并完全支持TypeScript(但仍使开发人员能够使用纯JavaScript进行编码),并结合了OOP(面向对象编程),FP(功能编程)和FRP(功能反应性编程)的元素。

    这次我就帮助童鞋快速入门,尽快应用起 nest,在实战中学习 nest,对工作和学习都有更好的帮助。

    1、利用基于 nest 快速搭建项目。
    2、学习如何连接 mysql 数据库,利用 typeorm 操作 mysql 数据库。
    3、创建用户模块,并进行 token 验证
    4、返回数据进行处理
    5、文件上传

    1、利用基于 nest 快速搭建项目

    使用 nest cli 快速创建项目

    $ npm i -g @nestjs/cli
    $ nest new project-name
    

    将创建 project 目录, 安装node模块和一些其他样板文件,并将创建一个 src 目录,目录中包含几个核心文件。

    src
    ├── app.controller.ts
    ├── app.module.ts
    └── main.ts
    

    以下是这些核心文件的简要概述:
    app.controller.ts 带有单个路由的基本控制器示例。
    app.module.ts 应用程序的根模块。
    main.ts 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。
    运行程序

    npm run start
    

    在应用程序运行时, 打开浏览器并访问 http://localhost:3000/。 应该看到 Hello world! 信息。

    2、学习如何连接 mysql 数据库,利用 typeorm 操作 mysql 数据库。

    typeorm 对 mysql 数据库版本有要求,需要5.6以上,在这个地方遇到过问题,找了很长时间,才发现这个问题。最好是使用最新的 mysql 数据库。
    运行命令安装 mysql、typeorm

    npm install --save @nestjs/typeorm typeorm mysql
    

    安装过程完成后,我们可以将 TypeOrmModule 导入AppModule 。
    app.module.ts

    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    
    @Module({
      imports: [
        TypeOrmModule.forRoot({
          type: 'mysql',
          host: 'localhost',
          port: 3306,
          username: 'root',
          password: 'root',
          database: 'test',
          entities: [],
          synchronize: true,
        }),
      ],
    })
    export class AppModule {}
    

    我们可以也创建 ormconfig.json ,而不是将配置对象传递给 forRoot()。

    {
      "type": "mysql",
      "host": "localhost",
      "port": 3306,
      "username": "root",
      "password": "root",
      "database": "test",
      "entities": ["dist/**/*.entity{.ts,.js}"],
      "synchronize": true
    }
    

    然后,我们可以不带任何选项地调用 forRoot() :
    app.module.ts

    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    
    @Module({
      imports: [TypeOrmModule.forRoot()],
    })
    export class AppModule {}
    

    创建实体,这里创建 admin 实体为例。

    import {
      Entity,
      PrimaryGeneratedColumn,
      Column,
      BeforeInsert,
      JoinTable,
      ManyToMany,
      OneToMany,
      BeforeUpdate
    } from 'typeorm';
    import { IsEmail } from 'class-validator';
    import * as argon2 from 'argon2';
    
    @Entity('admin')
    export class AdminEntity {
    
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column()
      username: string;
    
    
      @Column()
      password: string;
    
      @BeforeInsert()
      async hashPassword() {
        this.password = await argon2.hash(this.password);
      }
    
    
    }
    
    

    admin.module.ts

    import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common';
    import { AdminController } from './admin.controller';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { AdminEntity } from './admin.entity';
    import { AdminService } from './admin.service';
    import { AuthMiddleware } from './auth.middleware';
    
    @Module({
      imports: [TypeOrmModule.forFeature([AdminEntity])],
      providers: [AdminService],
      controllers: [
        AdminController
      ],
      exports: [AdminService]
    })
    

    此模块使用 forFeature() 方法定义在当前范围中注册哪些存储库。这样,我们就可以使用 @InjectRepository()装饰器将 adminRepository 注入到 AdminService 中:

    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository, getRepository, DeleteResult } from 'typeorm';
    import { AdminEntity } from './admin.entity';
    import {UserDto, UserPassDto} from './dto';
    const jwt = require('jsonwebtoken');
    import { SECRET } from '../config';
    import { validate } from 'class-validator';
    import { HttpException } from '@nestjs/common/exceptions/http.exception';
    import { HttpStatus } from '@nestjs/common';
    import * as argon2 from 'argon2';
    import {MsgService} from "../msg/msg.service";
    
    
    @Injectable()
    export class AdminService {
      constructor(
        @InjectRepository(AdminEntity)
        private readonly adminRepository: Repository<AdminEntity>,
        private readonly MSG: MsgService
      ) {}
    
      async findAll(): Promise<AdminEntity[]> {
        return await this.adminRepository.find();
      }
    
      async findOne({username, password}: UserDto): Promise<AdminEntity> {
        const user = await this.adminRepository.findOne({username});
        if (!user) {
          return null;
        }
    
        if (await argon2.verify(user.password, password)) {
          return user;
        }
    
        return null;
      }
    }
    
    

    3、创建用户模块,并进行 token 验证

    使用 nest 命令创建用户模块

     nest g module admin
     nest g service admin
     nest g controller admin
    

    这里我们使用中间件middleware来做用户的 token 验证

    中间件是在路由处理程序 之前 调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。 next() 中间件函数通常由名为 next 的变量表示。

    图1

    Nest 中间件实际上等价于 express 中间件。 下面是Express官方文档中所述的中间件功能:

    中间件函数可以执行以下任务:

    • 执行任何代码。
    • 对请求和响应对象进行更改。
    • 结束请求-响应周期。
    • 调用堆栈中的下一个中间件函数。
    • 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。

    您可以在函数中或在具有 @Injectable() 装饰器的类中实现自定义 Nest中间件。 这个类应该实现 NestMiddleware 接口, 而函数没有任何特殊的要求。

    import { HttpException } from '@nestjs/common/exceptions/http.exception';
    import { NestMiddleware, HttpStatus, Injectable } from '@nestjs/common';
    import { ExtractJwt, Strategy } from 'passport-jwt';
    import { Request, Response, NextFunction } from 'express';
    import * as jwt from 'jsonwebtoken';
    import { SECRET } from '../config';
    import { AdminService } from './admin.service';
    import {MsgService} from "../msg/msg.service";
    
    @Injectable()
    export class AuthMiddleware implements NestMiddleware {
      constructor(private readonly userService: AdminService,
                  private readonly MSG: MsgService) {}
    
      async use(req: Request, res: Response, next: NextFunction) {
        const authHeaders = req.headers.authorization;
        if (authHeaders) {
          const token = authHeaders;
    
          const decoded: any = this.verify(token);
          const user = await this.userService.findById(decoded.id);
    
          if (!user) {
            throw new HttpException('User not found.', HttpStatus.UNAUTHORIZED);
          }
    
          req.user = user;
          next();
    
        } else {
          throw new HttpException('Not authorized.', HttpStatus.UNAUTHORIZED);
        }
      }
      private verify (token:string){
        let decoded: any
        try {
          decoded = jwt.verify(token, SECRET);
        }catch (e) {
          this.MSG.fail('token error')
        }
       return decoded
      }
    }
    
    

    在admin.module.ts中应用AuthMiddleware,forRoutes里面配置的路径是 controller 里需要进行 token 验证的接口路径

    import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common';
    import { AdminController } from './admin.controller';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { AdminEntity } from './admin.entity';
    import { AdminService } from './admin.service';
    import { AuthMiddleware } from './auth.middleware';
    
    @Module({
      imports: [TypeOrmModule.forFeature([AdminEntity])],
      providers: [AdminService],
      controllers: [
        AdminController
      ],
      exports: [AdminService]
    })
    export class AdminModule implements NestModule {
      public configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(AuthMiddleware)
          .forRoutes({path: 'admin/users', method: RequestMethod.GET}, {path: 'user', method: RequestMethod.PUT});
      }
    }
    
    

    admin.controller.ts

    import { Get, Post, Body, Put, Delete, Param, Controller, UsePipes } from '@nestjs/common';
    import { Request } from 'express';
    import { AdminService } from './admin.service';
    import {UserDto, UserPassDto} from './dto';
    import { HttpException } from '@nestjs/common/exceptions/http.exception';
    import { ValidationPipe } from '../shared/pipes/validation.pipe';
    
    import {
      ApiBearerAuth, ApiTags
    } from '@nestjs/swagger';
    import {MsgService} from "../msg/msg.service";
    
    @ApiBearerAuth()
    @ApiTags('admin')
    @Controller('admin')
    export class AdminController {
    
      constructor(private readonly userService: AdminService,
                  private readonly MSG: MsgService) {}
    
      @Get('users')
      async getall(){
        return this.userService.findAll();
      }
    
      @UsePipes(new ValidationPipe())
      @Put('users')
      async create(@Body() userData: UserDto) {
        return this.userService.create(userData);
      }
    
      @UsePipes(new ValidationPipe())
      @Post('users/login')
      async login(@Body() loginUserDto: UserDto) {
        console.log(loginUserDto)
        const _user = await this.userService.findOne(loginUserDto);
    
        if (!_user) this.MSG.fail('no user')
    
        const token = await this.userService.generateJWT(_user);
        const { username} = _user;
        const user = {token, username};
        return {user}
      }
      @Post('change/pass')
      async changePass(@Body() changePassBody: UserPassDto){
        return this.userService.changePass(changePassBody)
      }
    }
    
    

    完整代码:https://github.com/baiqingchun/nest/tree/master/src/admin

    4、返回数据进行处理

    这里的返回数据我们并不满意,希望可以反回类似

     {
                        data:{},
                        code: 200,
                        message: '请求成功',
    }
    

    那我们要在每个请求后面都进行这样的数据结构的整理,这样太麻烦了,可以使用interceptor拦截器来处理返回数据
    transform.interceptor.ts

    import {
        Injectable,
        NestInterceptor,
        CallHandler,
        ExecutionContext,
    } from '@nestjs/common';
    import { map } from 'rxjs/operators';
    import { Observable } from 'rxjs';
    interface Response<T> {
        data: T;
    }
    @Injectable()
    export class TransformInterceptor<T>
        implements NestInterceptor<T, Response<T>> {
        intercept(
            context: ExecutionContext,
            next: CallHandler<T>,
        ): Observable<Response<T>> {
            return next.handle().pipe(
                map(data => {
                    return {
                        data,
                        code: 200,
                        message: '请求成功',
                    };
                }),
            );
        }
    }
    

    对异常返回处理
    使用Exception filters异常过滤器。
    http-exception.filter.ts

    import {
      ArgumentsHost,
      Catch,
      ExceptionFilter,
      HttpException,
      HttpStatus,
      Logger,
    } from '@nestjs/common';
    import {QueryFailedError} from "typeorm";
    
    @Catch(HttpException)
    export class HttpExceptionFilter implements ExceptionFilter {
      catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse();
        const request = ctx.getRequest();
    
        const message = exception.message;
        Logger.log('错误提示', message);
        const errorResponse = {
          message: message,
          code: exception.getStatus(), // 自定义code
          url: request.originalUrl, // 错误的url地址
        };
        const status =
            exception instanceof HttpException
                ? exception.getStatus()
                : HttpStatus.INTERNAL_SERVER_ERROR;
        // 设置返回的状态码、请求头、发送错误信息
        response.status(status);
        response.header('Content-Type', 'application/json; charset=utf-8');
        response.send(errorResponse);
      }
    }
    
    

    如果觉得平常写抛出异常太长太麻烦,可以对抛出异常再进行封装。

       throw new HttpException({message: 'reason'},HttpStatus.BAD_REQUEST);
    

    改为

       this.MSG.fail('Username must be unique',400)
    

    this.MSG是对异常的封装

    import {HttpException, HttpStatus, Injectable} from '@nestjs/common';
    
    @Injectable()
    export class MsgService {
     public fail(reason:string, code?:number){
         if (code) {
             throw new HttpException({message: reason},code);
         } else {
             throw new HttpException({message: reason},HttpStatus.BAD_REQUEST);
         }
     }
    
    
    

    完整代码:https://github.com/baiqingchun/nest/tree/master/src/msg

    5、文件上传

    一个项目离不开文件上传,nest 对文件上传也做了相应处理
    创建文件模块。
    如果需要详细理解文件上传,可以看另外一篇文章https://www.jianshu.com/p/28f8dd9a732e

    还是使用命令来做

     nest g module file
     nest g service file
     nest g controller file
    

    file.module.ts

    import { Module } from '@nestjs/common';
    import { FileController } from './file.controller';
    import { FileService } from './file.service';
    import { MulterModule } from '@nestjs/platform-express';
    import dayjs = require('dayjs');
    import { diskStorage } from 'multer';
    import * as nuid from 'nuid';
    @Module({
      imports:[
        MulterModule.register({
          storage: diskStorage({
            // 配置文件上传后的文件夹路径
            destination: `./public/uploads/${dayjs().format('YYYY-MM-DD')}`,
            filename: (req, file, cb) => {
              // 在此处自定义保存后的文件名称
              // const filename = `${nuid.next()}.${file.mimetype.split('/')[1]}`;
              // return cb(null, filename);
              return  cb(null, file.originalname);
            },
          }),
        }),
    
      ],
      controllers: [FileController],
      providers: [FileService]
    })
    export class FileModule {}
    
    

    file.controller.ts

    import { Controller, Post, UseInterceptors, UploadedFile, UploadedFiles, Body } from '@nestjs/common';
    import {
      AnyFilesInterceptor,
      FileFieldsInterceptor,
      FileInterceptor,
      FilesInterceptor,
    } from '@nestjs/platform-express';
    import multer = require('multer');
    // import {StorageService} from 'src/common/storage/storage.service'
    import {FileService} from './file.service'
    @Controller('file')
    export class FileController {
      constructor(
        private readonly fileService:FileService,
      ) {
      }
      @Post('upload')
      @UseInterceptors(FileInterceptor('file'))
      async UploadedFile(@UploadedFile() file) {
        // 这里的 file 已经是保存后的文件信息了,在此处做数据库处理,或者直接返回保存后的文件信息
        // const buckets =await this.storageService.getBuckets()
        console.log(file)
        return file;
      }
    
      @Post('upload3')
      @UseInterceptors(FilesInterceptor('file'))
      uploadFile(@UploadedFiles() file) {
        console.log(file);
      }
    
      @Post('uploads')
      @UseInterceptors(FileFieldsInterceptor([
        { name: 'avatar', maxCount: 1 },
        { name: 'background', maxCount: 1 },
        { name: 'avatar_name' },
        { name: 'background_name'}
      ]))
      async uploads(@UploadedFiles() files,@Body() body) {
        console.log(files,body)
      }
    
    

    总结

    至此关于 nest 实战结束,希望对你有所帮助。全部代码https://github.com/baiqingchun/nest

    相关文章

      网友评论

        本文标题:nest mysql 实战

        本文链接:https://www.haomeiwen.com/subject/uypaqktx.html