Skip to content

Nest框架 MySQL + TypeORM + JWT 实现登录注册

Published: at 09:06 AM

Mysql

创建个新的 database

CREATE SCHEMA login_test DEFAULT CHARACTER SET utf8mb4;

安装 typeorm 相关的包

npm install --save @nestjs/typeorm typeorm mysql2

在开始之前,首先明确什么是ORM?

AppModule

引入相关的 ORM库 TypeOrmModule,传入 option

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UserModule } from "./user/user.module";
import { JwtModule } from "@nestjs/jwt";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "mysql",
      host: "localhost",
      port: 3306,
      username: "root",
      password: "your password",
      database: "login_test",
      synchronize: true,
      logging: true,
      entities: [],
    }),
    JwtModule.register({
      global: true,
      secret: "huang",
      signOptions: {
        expiresIn: "7d",
      },
    }),
    UserModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

这里主要是数据库配置项,通常只需要配置 type, host, port, username, password, database

{
  "type": "mysql", //
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "your password",
  "database": "login_test",
  "synchronize": true,
  "logging": true,
  "entities": []
}

现在给 Nest 快速创建一个 User 的 CRUD 模块

nest g resource user

随即在 app.module.ts 中引入 User 的 Entity

import { User } from "./user/entities/user.entity";
entities: [User];

Entity 实体类

import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";

@Entity() //标记表明这是一个实体类
export class User {
  @PrimaryGeneratedColumn() //默认 int 自增
  id: number;

  @Column({
    length: 50,
    comment: "用户名",
  })
  username: string;

  @Column({
    length: 50,
    comment: "密码",
  })
  password: string;

  @CreateDateColumn({
    comment: "创建时间",
  })
  createTime: Date;

  @UpdateDateColumn({
    comment: "更新时间",
  })
  updateTime: Date;
}

然后跑下服务

npm run start:dev

error.png

在 UserModule 模块 引入 TypeOrm.forFeature 动态模块,传入 User 的 entity

import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entities/user.entity";

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

在 service 模块注入对应的 Repository

@Injectable()
export class UserService {
  @InjectRepository(User)
  private userRepository: Repository<User>;
}

随后在 UserController 分别添加 login、register 方法

import { Body, Controller, Inject, Post } from "@nestjs/common";
import { UserService } from "./user.service";

@Controller("user")
export class UserController {
  constructor(private readonly userService: UserService) {}
  @Post("login")
  async login() {}
  @Post("register")
  register() {}
}

接下来就是构造 dto 的时间了,这里不清楚 dto 作用的可以看之前发过的文章

export class LoginDto {
  username: string;
  password: string;
}
export class RegisterDto {
  username: string;
  password: string;
}

UserController

import { Body, Controller, Inject, Post } from "@nestjs/common";
import { UserService } from "./user.service";

@Controller("user")
export class UserController {
  constructor(private readonly userService: UserService) {}
  @Post("login")
  async login(@Body() user: LoginDto) {
    console.log(user);
  }
  @Post("register")
  register(@Body() user: RegisterDto) {
    console.log(user);
  }
}

然后去实现具体的功能,先实现注册

import { Body, Controller, Inject, Post } from "@nestjs/common";
import { UserService } from "./user.service";

@Controller("user")
export class UserController {
  constructor(private readonly userService: UserService) {}
  @Post("register")
  async register(@Body() user: RegisterDto) {
    return await this.userService.register(user);
  }
}

接下来看看 UserService 注册具体逻辑怎么实现

import { RegisterDto } from "./dto/register.dto";
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./entities/user.entity";
import * as crypto from "crypto";

function md5(str) {
  const hash = crypto.createHash("md5");
  hash.update(str);
  return hash.digest("hex");
}

@Injectable()
export class UserService {
  private logger = new Logger();

  @InjectRepository(User)
  private userRepository: Repository<User>;

  async register(user: RegisterDto) {
    const foundUser = await this.userRepository.findOneBy({
      username: user.username,
    });

    if (foundUser) {
      throw new HttpException("用户已存在", 200);
    }

    const newUser = new User();
    newUser.username = user.username;
    newUser.password = md5(user.password);

    try {
      await this.userRepository.save(newUser);
      return "注册成功";
    } catch (e) {
      this.logger.error(e, UserService);
      return "注册失败";
    }
  }
}

JWT 集成

先安装下 jwt 相关包

npm install @nestjs/jwt

在 AppModule 里引入 JwtModule

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UserModule } from "./user/user.module";
import { User } from "./user/entities/user.entity";
import { JwtModule } from "@nestjs/jwt";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "mysql",
      host: "localhost",
      port: 3306,
      username: "root",
      password: "xxx",
      database: "login_test",
      synchronize: true,
      logging: true,
      entities: [User],
    }),
    JwtModule.register({
      global: true,
      secret: "huang",
      signOptions: {
        expiresIn: "7d",
      },
    }),
    UserModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

在 UserController 注入 JwtService

import { Body, Controller, Inject, Post, Res } from "@nestjs/common";
import { UserService } from "./user.service";
import { LoginDto } from "./dto/login.dto";
import { RegisterDto } from "./dto/reagister.dto";
import { JwtService } from "@nestjs/jwt";
import { Response } from "express";

@Controller("user")
export class UserController {
  constructor(private readonly userService: UserService) {}
  @Inject(JwtService)
  private jwtService: JwtService; //jwt 注入
  @Post("login")
  async login(
    @Body() user: LoginDto,
    @Res({ passthrough: true }) res: Response
  ) {
    const foundUser = await this.userService.login(user);
    if (foundUser) {
      //将登陆成功后的用户信息加载到jwt,生产token,设置header头,返回到客户端
      const token = await this.jwtService.signAsync({
        user: {
          id: foundUser.id,
          username: foundUser.username,
        },
      });
      res.setHeader("token", token); //登录成功,将 user 信息放到 jwt 通过 header 返回
      return "login success";
    } else {
      return "login failed";
    }
  }
  @Post("register")
  register(@Body() user: RegisterDto) {
    console.log(user);
    this.userService.register(user);
  }
}

error.png error.png error.png

Guard 守卫

nest g guard login --no-spec --flat

jwt 验证逻辑

import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class LoginGuard implements CanActivate {
  @Inject(JwtService)
  private jwtService: JwtService;

  canActivate(
    context: ExecutionContext
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    //取出 authorization 中的 header,验证 token 有效返回 true,无效则抛出异常
    const authorization = request.header("autho rization") || "";
    const bearer = authorization.split(" ");
    if (!bearer || bearer.length < 2) {
      throw new UnauthorizedException("登录 token 错误");
    }
    const token = bearer[1];
    try {
      const info = this.jwtService.verify(token);
      (request as any).user = info.user;
      return true;
    } catch (e) {
      throw new UnauthorizedException("登录 token 失效,请重新登录");
    }
  }
}

接下来把 LoginGuard 应用起来

import { Controller, Get, UseGuards } from "@nestjs/common";
import { AppService } from "./app.service";
import { LoginGuard } from "./login.guard";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
  @UseGuards(LoginGuard)
  @Get("aaa")
  foo() {
    return "token 验证成功!!";
  }
}