likes
comments
collection
share

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

作者站长头像
站长
· 阅读数 12

完善用户表

在篇幅一中已经完成了用户表的创建,只有两个字段是不符合我们后续开发的要求,目前关于用户表的设计包含:id用户名昵称密码角色生日头像性别邮箱等等,其中在注册的时候用户名和密码是不能为空的,其他的看自己要求

在后续的表创建中都会涉及到几个字段:id创建时间更新时间 所以我们可以把他们单独抽离一个文件 base.entity.ts

在根目录的libs下面创建文件 Entities/base.entity.ts 内容如下:

import { CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Base {
  @PrimaryGeneratedColumn('uuid')
  id: string; // 主键,自动生成

  @CreateDateColumn({
    type: 'timestamp',
    nullable: false,
    comment: '创建时间',
  })
  create_at: Date;

  @CreateDateColumn({
    type: 'timestamp',
    nullable: false,
    comment: '更新时间',
  })
  update_at: Date;
}

然后修改User模块中设置用户实体user/entities/user.entity.ts文件

import { Base } from 'libs/Entities/base.entity';
import { Column, Entity } from 'typeorm';

@Entity('user')
export class User extends Base {
  @Column({
    type: 'varchar',
    length: 50,
    unique: true,
    comment: '用户名',
  })
  username: string;

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

  @Column({
    nullable: true,
    comment: '角色',
  })
  role: string;

  @Column({
    nullable: true,
    comment: '昵称',
  })
  nickname: string;

  @Column({
    nullable: true,
    comment: '年龄',
  })
  age: number;

  @Column({
    nullable: true,
    comment: '头像',
  })
  avatar: string;

  @Column({
    nullable: true,
    comment: '性别',
  })
  sex: string;

  @Column({
    nullable: true,
    comment: '邮箱',
  })
  email: string;
}

表设计完成之后开始实现注册功能,在注册过程中首先判断注册的用户是否已经存在数据库中,如果已经存在抛出相对应的状态码以及错误信息,否则就向数据库中添加一条用户数据,同时在存入数据库的时候要对密码进行加密

首先针对账号和密码做一些规则校验:不能为空,长度限制等等,我们这里使用ValidationPipe + class-validator

安装 class-validator 和 class-transformer 的包:

pnpm install class-validator

CreateUserDto 里声明参数的约束条件:

import { IsString, IsNotEmpty, Length, Matches } from 'class-validator';
export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  @Length(6, 30)
  @Matches(/^[a-zA-Z0-9#$%_-]+$/, {
    message: '用户名只能是字母、数字或者 #、$、%、_、- 这些字符',
  })
  username: string;

  @IsString()
  @IsNotEmpty()
  @Length(6, 30)
  password: string;
}

首先在user/dto/create-user.dto.ts 文件里面添加两个字段

export class CreateUserDto {
  username: string;
  password: string;
}

其次在user.controller.ts 文件里面添加注册register接口,并且使用 ValidationPipe 进行约束

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

对于密码要进行加密处理,为了更好的复用,我们在src目录下面创建utils/md5.ts文件,并且使用crypto包进行加密:

首先安装crypto:

pnpm install crypto

创建md5加密方法

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

import * as crypto from 'crypto';

/**
 *
 * @param str 要加密解密的字符
 * @returns
 */
export function md5(str: string): string {
  if (!str) return '';
  const hash = crypto.createHash('md5');
  return hash.update(str).digest('hex');
}

然后在user.service.ts 文件里面编写注册逻辑

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现


  async register(createUserDto: CreateUserDto) {
    const { username, password } = createUserDto;
    const existUser = await this.userRepository.findOneBy({
      username,
    });
    if (existUser) {
      throw new HttpException('用户名已存在', 200);
    }
    // 对密码进行加密
    const newUser = new User();
    newUser.username = username;
    newUser.password = md5(password);
    try {
      return await this.userRepository.save(newUser);
    } catch (e) {
      throw new HttpException(e, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

然后进行接口注册测试:

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 但是返回的信息包含密码,虽然已经加密处理,首先在 user.entity.ts 做一下修改

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 然后修改 user.module.ts

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 再次注册

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

注册接口就告一段落了,接下来开始实现登录功能

登录功能

首先在 user.controller.ts 添加登录接口

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

然后在 user.service.ts 里面添加登录的方法

  // 登录
  async login(createUserDto: CreateUserDto) {
    const { username, password } = createUserDto;

    const existUser = await this.userRepository.findOne({
      where: {
        username,
      },
      select: ['username', 'password'],
    });
    if (!existUser) {
      throw new HttpException('用户名不存在', 200);
    }

    if (existUser.password != md5(password)) {
      throw new HttpException('密码错误,请重新登录', 200);
    }
    return existUser;
  }

然后登录测试

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 这里就已经登录成功了,之后我们我们要用到JWT来根据用户名和密码生成一个token返回给前端,前端每次请求后端都会校验token是否失效

安装 @nestjs/jwt

pnpm install @nestjs/jwt

修改default 环境配置

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

DB_HOST = localhost
DB_PORT = 3306
DB_USERNAME = root
DB_PASSWORD = 123456
DB_DATABASE = manageadmin
DB_SYNC = true

# 服务端口号
SERVE_PORT = 3333

# JWT密码/失效时间
JWT_SECRET = dapeng
JWT_EXPIRESIN = 7d

然后修改我们的 db.module.ts

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

    JwtModule.registerAsync({
      global: true,
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        secret: configService.get('JWT_SECRET'),
        signOptions: {
          expiresIn: configService.get('JWT_EXPIRESIN'),
        },
      }),
    }),

修改 user.control.ts 的登录方法

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 再次登录能够看到生成的token

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

使用Guard限制访问

前端登录之后拿到token,在后面的所有请求会把token放到请求头里面,后端需要拿到这个token看是否过期,如果已经过期返回前端重新登录,我们可以自定义一个装饰器Guard,在每个请求都加上进行约束 执行nest g guard Jwt.auth --no-spec --flat 生成一个Jwt.auth.guard.ts文件,如果让选择是在src还是db下面,选择src即可(看自己),没有的话就不用不管

nest g guard Jwt.auth --no-spec --flat

修改jwt.auth.guard.ts

import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { ConfigService } from '@nestjs/config';
import { Reflector } from '@nestjs/core';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
  ) {}
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request: Request = context.switchToHttp().getRequest(); // 获取请求头

    const token = this.extractTokenFromHeader(request); // 获取请求中的token字段
    if (!token) {
      throw new UnauthorizedException('登录 token 错误,请重新登录');
    }
    try {
      const payload = await this.jwtService.verify(token, {
        secret: this.configService.get('JWT_SECRET'),
      });
      request['user'] = payload;
    } catch (e) {
      throw new UnauthorizedException('身份过期,请重新登录');
    }

    return true;
  }

  private extractTokenFromHeader(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

修改 user.controller.ts, 在findAll 添加装饰器 UseGuards,测试Jwt鉴权

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

 @Inject(JwtService)
  private jwtService: JwtService;

  @Post('login')
  async login(@Body() createUserDto: CreateUserDto) {
    const foundUser = await this.userService.login(createUserDto);
    if (foundUser) {
      const payload = {
        username: foundUser.username,
        sub: foundUser.id,
      };
      return this.jwtService.sign({
        user: payload,
      });
    }
  }

  @Get()
  @UseGuards(JwtAuthGuard)
  findAll() {
    return this.userService.findAll();
  }

使用账号登录获取token ,然后再查询用户接口的请求头添加 Authorization 值为:Bearer 加token

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 能够正常的查询到数据

白名单和全局Guard

用户登录之后所有的方法都要加上Jwt鉴权,我们需要把JwAuthGuard 设置为全局Guard

app.module.ts文件里面

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },

然后去掉刚才在findAll的Jwt鉴权UseGuards

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 然后再次查询用户数据也是能够正常返回的,这个时候Jwt全局鉴权已经添加完毕,但是当我们执行登录的时候不应该进行鉴权的

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 接下来是针对一些不需要鉴权的接口单独处理,首先创建文件utils/public.ts

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 @SetMetadata() 装饰器来为路由处理方法设置元数据,这里设置元数据IsPublic为true

import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

然后在不需要鉴权的地方添加修饰器@Public()

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 这样就可以在jwt.auth.guard.ts 里面通过Reflector 取出当前的isPublic,如果为true(使用了@Public()装饰过的接口),就不进行鉴权

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

    // 一些接口不需要进行认证(方法和白名单路由差不多)
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

接下来就开始设置白名单,对于一些请求直接放行,不需要鉴权,首先在jwt.auth.guard.ts里面添加白名单,以及判断白名单的逻辑

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

    // 白名单放行
    if (this.hasUrl(this.urlList, request.url)) {
      return true;
    }
    
  private urlList: string[] = ['/user/login', '/user/register']; // 验证该次请求是否为白名单内的路由

  private hasUrl(urlList: string[], url: string): boolean {
    const flag = urlList.indexOf(url) >= 0;
    return flag;
  }

前几步我们给login路由添加了@Pulic()装饰,现在我们把他去掉,来验证白名单是否生效

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现

Vue3 + Nest 实现博客管理系统 后端篇(二):用户表设计及登录注册功能实现 token正常返回,白名单生效,好了,本章节学习就告一段落了

转载自:https://juejin.cn/post/7265517464834211892
评论
请登录