本周主要实现注册和登录功能。
注册与登录
创建
创建有关login的文件,包括控制器、DTO、Service等
DTO
1.创建dto,与前端讨论需要的数据传输对象,创建文件login.dto.ts,里面创立两个类别,LoginDto与
RegisterDto
登陆的参数解释:
参数解释 | 名称 | 数据类型 | 限制 |
---|---|---|---|
登录账户 | phonenumber | string | 不超过11个字符电话号码格式 |
密码 | password | string | 8-16位 |
用户类型 | user_type | int | 1,2,3之间 |
用户类型1对应普通用户
用户类型2对应管理员
用户类型3对应超管
注册的参数解释:
参数解释 | 名称 | 数据类型 | 限制 |
---|---|---|---|
登录账户 | phonenumber | string | 不超过11个字符电话号码格式 |
密码 | password | string | 8-16位 |
邮箱 | string | 邮箱格式 |
注册只为用户开放 管理员不能注册 是超管账户去后台直接添加 或者数据库里直接加.
登录:
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;
总结:
本周主要实现了登录和注册,也实现了数据库的连接。
下周主要编写用户部分,需要学习的内容是解决权限的问题,工作人员和管理员的后台逻辑问题等。