毕业设计第十周

本文档详细介绍了使用NestJS实现用户注册和登录功能,包括DTO创建、参数解释、异常处理、JWT Token验证以及密码加密。还涵盖了用户类型定义、登录异常类、工具函数如jwt.ts、encryptPassword.ts和token.ts的实现。最后,展示了登录和注册的Controller及Service的代码,并提供了Swagger测试步骤。此外,讨论了下周工作重点——权限管理和后台逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本周主要实现注册和登录功能。

注册与登录

创建

创建有关login的文件,包括控制器、DTO、Service等

DTO

1.创建dto,与前端讨论需要的数据传输对象,创建文件login.dto.ts,里面创立两个类别,LoginDto与
RegisterDto
登陆的参数解释:

参数解释名称数据类型限制
登录账户phonenumberstring不超过11个字符电话号码格式
密码passwordstring8-16位
用户类型user_typeint1,2,3之间

用户类型1对应普通用户
用户类型2对应管理员
用户类型3对应超管

注册的参数解释:

参数解释名称数据类型限制
登录账户phonenumberstring不超过11个字符电话号码格式
密码passwordstring8-16位
邮箱emailstring邮箱格式

注册只为用户开放 管理员不能注册 是超管账户去后台直接添加 或者数据库里直接加.

在这里插入图片描述

登录:

import { ApiProperty } from '@nestjs/swagger';
import {
  IsIn,
  IsNotEmpty,
  IsNumber,
  IsString,
  Matches,
  MaxLength,
  MinLength,
} from 'class-validator';

/**
 * 所有用户
 */
export class LoginDto {
  @ApiProperty({ example: '13220222022' })
  @IsString({ message: '电话号码需要是字符串' })
  @MaxLength(11, { message: '电话号码不能超过11个字符' })
  @IsNotEmpty({ message: '电话号码不能为空' })
  @Matches(/^(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/, {
    message: '电话号码格式不正确',
  })
  readonly phonenumber: string;

  @ApiProperty({ example: '123456!@#' })
  @IsString({ message: '密码需要是字符串' })
  @MaxLength(16, { message: '密码不能超过16个字符' })
  @MinLength(8, { message: '密码不能少于8位' })
  @IsNotEmpty({ message: '密码不能为空' })
  readonly password: string;

  @ApiProperty({ example: 3 })
  @IsNumber()
  @IsIn([1, 2, 3], { message: '类型只能时1.2.3中的一个' })
  readonly user_type: number;
}

注册:

/**
 *  注册仅提供用户注册,管理员需要后台直接添加
 */
export class RegisterDto extends LoginDto {
  @ApiProperty({ example: '1234@qq.com' })
  @IsString({ message: '邮箱需要是字符串' })
  @MaxLength(50, { message: '邮箱不能超过50个字符' })
  @IsNotEmpty({ message: '邮箱不能为空' })
  @Matches(/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/, {
    message: '邮箱格式不正确',
  })
  readonly email: string;

  @ApiProperty({ example: '名称' })
  @IsString({ message: '用户名称需要是字符串' })
  @MaxLength(50, { message: '用户名称不能超过50个字符' })
  @IsNotEmpty({ message: '用户名称不能为空' })
  readonly user_name: string;

  @ApiProperty({ example: 1 })
  @IsNumber()
  @IsIn([1], { message: '注册类型只能是1' })
  readonly user_type: number;
}

HttpException

定义登陆异常处理类报错信息与状态码,创建文件login.exception.ts

import { HttpStatus, HttpException } from '@nestjs/common';

/**
 * 默认
 * msg = '登录错误', code = 401
 * 可自定义
 */

export class LoginException extends HttpException {
  constructor(msg = '登录错误', code = 401) {
    super(msg, code);
  }
}

登录错误后,返回登陆错误的信息,状态码为401.

登陆验证

JWT–Token(令牌)验证,学习链接1: link1.
学习链接2: link2.

jwt 全称jsonwebtoken 说简单点就是一个加密的东西, 他根据你给的内容生成字符串,前端用户需要提供这个字符串,然后后端解析这个字符串,来判断这个用户是否登陆成功.

在 src 目录下,新建文件夹 utils,里面将存放各种工具函数,
在这里插入图片描述

1.创建工具函数jwt.ts实现验证

创建工具函数jwt.ts
jwt这个也是工具函数,用于生成jsonwebtoken 就是很长一个字符串 主要函数是一个生成这个字符串 然后另外一个是解密这字符串

import * as jwt from 'jsonwebtoken';
import config from '../config';

/**
 *
 * @param data  要经过加密的数据
 * @returns token
 */
export const generateToken = (data: object) => {
  var token = jwt.sign(data, config.jwt.secret, {
    algorithm: config.jwt.alg,
    expiresIn: config.jwt.expire,
  });
  return token;
};

/**
 *
 * @param token  token字符串
 * @returns
 */
export const verifyToken = (token: string) => {
  console.log(token);
  var data = jwt.verify(token, config.jwt.secret);
  return data;
};

在这里插入图片描述

在配置config中,alg 表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256)
在这里插入图片描述

2.创建工具函数encryptPassword.ts实现加密

查看教程link.
encryptPassword 这个文件实现的是传入一个字符串 然后给你加密 这里是用于密码的。
密码不要明文存储 根据用户的输入的密码,在加密一次存储到数据库,登录时,在把输入的加密一次,和数据库的比对,一样就登陆成功了

import * as crypto from 'crypto';
import config from '../config';
/**
 * Make salt
 */
export function makeSalt(): string {
  return crypto.randomBytes(3).toString('base64');
}

/**
 * Encrypt password
 * @param password 密码
 */
export function encryptPassword(password: string): string {
  if (!password) {
    return '';
  }
  const tempSalt = Buffer.from(config.salt, 'base64');
  return (
    // 10000 代表迭代次数 16代表长度
    crypto.pbkdf2Sync(password, tempSalt, 10000, 16, 'sha1').toString('base64')
  );
}

在这里插入图片描述

3.创建工具函数token.ts实现校验

token.ts 是用户校验用户是否登录了,因为要登录了才能去发请求

import { ExecutionContext, HttpException } from '@nestjs/common';

import { LoginException } from 'src/exception/login.exception';
import { verifyToken } from 'src/utils/jwt';

/**
 * 前端请求格式:header中:Authorization:Bearer token....
 *  从请求上下文中获取到token
 * @param context
 * @returns token
 */
const getToken = async (context: ExecutionContext): Promise<string> => {
  try {
    const request = context.switchToHttp().getRequest();
    const authorization = request.header('Authorization') || 'Bearer  ';
    const bearer = authorization.split(' ')[0];
    const token = authorization.split(' ')[1];
    if (bearer !== 'Bearer') {
      throw new LoginException('token格式不正确');
    }
    // token
    return token;
  } catch (error) {
    throw new LoginException('token获取错误,请重新登录');
  }
};

/**
 *  从上下文中获取角色信息,依赖token
 * @param context 上下文
 * @returns
 */
export const getRoles = async (context: ExecutionContext): Promise<string> => {
  try {
    // 获取token
    const tokenStr = await getToken(context);
    // 解析token、
    const user = verifyToken(tokenStr);
    // 从解析token中判断是否存在

    if (!user) {
      throw new HttpException('token无效,请重新登录', 403);
    }
    let type = user.user_type;
    //  硬编码
    switch (type) {
      case 1:
        return 'user';

      case 2:
        return 'admin';

      case 3:
        return 'super';

      default:
        break;
    }
  } catch (error) {
    console.log('getRoles:error', error);
    throw new HttpException('token错误,无权访问', 403);
  }
};

/**
 * 根据请求上下文获取token后判断token是否合法
 * @param context  请求上下文
 * @returns  boolean
 */
export const isLogin = async (context: ExecutionContext): Promise<boolean> => {
  try {
    // 获取token
    const tokenStr = await getToken(context);

    // 解析token、
    const user = verifyToken(tokenStr);
    // 从解析token中判断是否存在
    if (!user) {
      throw new HttpException('token无效,请重新登录', 403);
    }
    return true;
  } catch (error) {
    throw new HttpException('token错误,请登陆后操作', 403);
  }
};

用户注册

创建login.service.ts

import {
  HttpException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UserEntity } from '../entity/user.entity';
import { LoginDto, RegisterDto } from '../dto/login.dto';
/* import { encryptPassword } from '../utils/encryptPassword'; */
import { generateToken } from '../utils/jwt';
import { LoginException } from 'src/exception/login.exception';
import { encryptPassword } from 'src/utils/encryptPassword';
@Injectable()
export class LoginService {
  constructor(
    @InjectRepository(UserEntity)
    private user: Repository<UserEntity>,
  ) {}

  /**
   *  用户和管理员都是同一个登录接口
   * @param LoginDto
   * @returns
   */
  async login(loginDto: LoginDto) {
    try {
      const user = await this.user.findOne({
        where: {
          phonenumber: loginDto.phonenumber,
          password: encryptPassword(loginDto.password),
          user_type: loginDto.user_type,
          // 未被删除的用户才能登录
          del_flag: '0',
        },
      });
      if (user) {
        // 生成token
        const token = await generateToken({
          user_id: user.user_id,
          user_type: user.user_type,
        });
        return { token, user_id: user.user_id, user_name: user.user_name };
      } else {
        throw new HttpException('登录失败,请检查账户密码以及登录类型', 403);
      }
    } catch (error) {
      throw new UnauthorizedException('登录失败,请检查账户密码以及登录类型');
    }
  }

  /**
   * 普通用户注册
   * @param registerDto
   * @returns
   */
  async register(registerDto: RegisterDto) {
    try {
      const phonenumber = registerDto.phonenumber;
      const password = encryptPassword(registerDto.password);
      const email = registerDto.email;
      const user_name = registerDto.user_name;
      const userIsExist = await this.IsExist(phonenumber);
      // 判断用户是否已注册 由于注册只会是普通用户,所以忽略管理员,管理员直接后台添加时,在判断。
      if (userIsExist.isExist) {
        throw new LoginException('用户已注册');
      }
      await this.user.insert({
        phonenumber,
        password,
        email,
        user_name,
        // 固定为普通用户
        user_type: 1,
      });
      return null;
    } catch (error) {
      throw new LoginException(error.message ?? '注册失败');
    }
  }

  /**
   * 根据手机号查看用户是否注册
   * @param phonenumber
   * @returns
   */
  async IsExist(phonenumber: string) {
    const user = await this.user.findOne({
      phonenumber,
    });
    console.log(user);
    if (!user) return { isExist: false };
    return { isExist: true };
  }
}

编写好后,在 login.controller.ts 中添加路由

import { Controller, Post, Body, HttpCode } from '@nestjs/common';
import { LoginService } from '../service/login.service';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { LoginDto, RegisterDto } from '../dto/login.dto';
@ApiTags('登录注册')
@Controller('auth')
export class LoginController {
  constructor(private readonly loginService: LoginService) {}

  @ApiOperation({ description: '所有人员的登录' })
  @Post('/login')
  @HttpCode(200)
  async login(@Body() LoginDto: LoginDto) {
    return this.loginService.login(LoginDto);
  }
  @ApiOperation({ description: '普通用户注册' })
  @Post('/register')
  @HttpCode(200)
  async register(@Body() registerDto: RegisterDto) {
    return this.loginService.register(registerDto);
  }
}

至此,方式总结为以下:

1.登录
请求方式: post
地址: /auth/login
返回方式:正确登录后生成token返回,前端再请求头中header中添加:Authorization:Bearer登录成功后返回的token

2.注册
请求方式:POST
地址: /auth/register
注册只为用户开放,通过该接口注册都是用户的注册

Swagger测试

运行后,无报错:
在这里插入图片描述

1.打开网址http://localhost:3000/doc/
在这里插入图片描述
在这里插入图片描述
2.点击注册
在这里插入图片描述
在这里插入图片描述
3.点击登录
在这里插入图片描述
在这里插入图片描述

复制token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTAwIiwidXNlcl90eXBlIjoxLCJpYXQiOjE2NTE3NTMyNDYsImV4cCI6MTY1MTc1Njg0Nn0.LzfkNCDYJ9XN-cQSPf_hCdUQ63I532WygKEvMDcxiOQ
4.未输入token时候点击评分创建
在这里插入图片描述
因为我们没有输入token,更改失败。
在这里插入图片描述
5.输入token后点击评分创建
在这里插入图片描述
在这里插入图片描述
6.成功之后显示:分数为59
在这里插入图片描述
刷新数据库,出现最新评分数据
在这里插入图片描述

到此,注册和登录功能测试成功。

7.打分的逻辑是根据表格来的,详情在grade.service.ts里面;
主要运用的是创建了工具函数sum:

let sum = (...args) => {
  var len = args.length;
  var s = 0;
  for (var i = 0; i < len; i++) {
    if (!isNaN(args[i])) {
      s += Number(args[i]);
    }
  }
  return s;
};
export default sum;

在这里插入图片描述
总结:
本周主要实现了登录和注册,也实现了数据库的连接。
下周主要编写用户部分,需要学习的内容是解决权限的问题,工作人员和管理员的后台逻辑问题等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值