天天看點

nest mysql 實戰

寫在前面

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

的變量表示。

nest mysql 實戰

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)
  }
}

           

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);
     }
 }


           

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 實戰

繼續閱讀