Nest全栈到失业(三):半小时图书管理系统-User
用户模块
创建用户
先使用nest g resource user --no-spec 创建一个用户的模块,并选择他的CRUD操作
写一个注册接口
import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { RegisterUserDto } from './dto/register-user.dto';@Controller('user')
export class UserController {// 注入user服务constructor(private readonly userService: UserService) {}@Post('register')create(@Body() registerUserDto: RegisterUserDto) {console.log(registerUserDto);return 'done';}
}
编写dto对象,就是数据传输对象,你前端给来的数据,需要是什么叫结构的,有哪些字段
export class RegisterUserDto {username: string;password: string;
}
测试,拿到数据了
数据校验
我们使用pipe管道的方式,结合第三方的库实现数据的校验,这是两个包
npm install --save class-transformer class-validator
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';async function bootstrap() {const app = await NestFactory.create(AppModule);// 使用全局的管道校验数据,并转为dtoapp.useGlobalPipes(new ValidationPipe({ transform: true }));await app.listen(process.env.PORT ?? 3000);
}bootstrap();
dto中限制
import { IsNotEmpty, MinLength } from 'class-validator';export class RegisterUserDto {@IsNotEmpty({ message: '用户名不能为空' })username: string;@IsNotEmpty({ message: '密码不能为空' })@MinLength(6, { message: '密码最少 4 位'})password: string;
}
测试
如果你的数据没有问题,就可以接到dto对象了
数据持久化
怎么办?还能怎么办,使用mysql呗
我们使用docker拉去mysql镜像,并创建一个新的数据库容器
不管你用什么方式,使用navicat也好,什么都行.连接到数据库,我使用的是webstorm全家桶的dataGrip 创建一个数据库
我们使用typeorm链接数据库,使用mysql2来做项目的转接
npm i --save @nestjs/typeorm typeorm mysql2 @types/node
使用最简单的方式(动态模块)链接数据库,因为我们的app模块是我们的默认必须的入口,所以直接在里边写链接mysql的动态配置吧
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { User } from './user/entities/user.entity';@Module({imports: [UserModule,TypeOrmModule.forRoot({type: 'mysql', //什么类型的数据库host: 'localhost', //端口ipport: 3306, //端口username: 'root', //用户名password: 'root', //密码database: 'book-end', //操作的数据库entities: [User], //注入那些实体synchronize: true, //同步构建数据库,真实开发千万别开logging: true, //日志打印等级connectorPackage: 'mysql2', //加密extra: {authPlugin: 'sha256_password', //加密},} as TypeOrmModuleOptions)],controllers: [AppController],providers: [AppService],
})
export class AppModule {
}
当然,在执行前,我们要写好自己的实体src/user/entities/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';@Entity()
export class User {// 定义自增的主键@PrimaryGeneratedColumn()id: number;// 定义其他特殊列@Column()username: string;@Column()password: string;
}
运行项目,npm run start:dev即可,它会自动链接数据库,并自动初始化数据库
测试一下吧?
我们在user.moudle中注入user这个实体,然后,我们在服务中使用一下
写一个路由
@Get()findAll(): Promise<User[]> {return this.userService.getAll();}
对应的操作数据库表的方法
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './entities/user.entity';@Injectable() export class UserService {constructor(// 使用仓库管理器方式,注入我们的数据库中的表@InjectRepository(User) private userRepository: Repository<User>) {}getAll(){return this.userRepository.find()}}
你一查,什么也没有,原因是,刚刚没链接数据,我们创建的,现在新创建的数据库,还没有数据呢,所以先注册再查询
因为我们有了数据库,所以创建用户也改改
// post请求@Post('register')// 使用接收body数据 ,数据必须符合registerUserDto的结构create(@Body() registerUserDto: RegisterUserDto) {return this.userService.create(registerUserDto)}
//报存到数据库create(registerUserDto: RegisterUserDto) {return this.userRepository.save(registerUserDto);}
然后你在查询就有了
走看看数据库去
这里我们没有做重复校验,其实很简单的,就是你创建的时候,先按照用户名的方式再数据库里查一遍,就好了
完善代码
其实一个模块无非就是几个点
创建实体
首先是我们创建了一个实体
那么你就要再使用实体的module中注入他
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';@Entity()
export class User {// 定义自增的主键@PrimaryGeneratedColumn()id: number;// 定义其他特殊列@Column()username: string;@Column()password: string;
}
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 {
}
分发路由
我们写好了实体,就去看看需要那些接口,写一写,当然了,我们的逻辑是不会写在contorller中的
import { Controller, Post, Body, Get } from '@nestjs/common';
import { UserService } from './user.service';
import { RegisterUserDto } from './dto/register-user.dto';
import { User } from './entities/user.entity';
import { LoginUserDto } from './dto/login-user.dto';@Controller('user')
export class UserController {// 注入user服务constructor(private readonly userService: UserService) {}@Get()findAll(): Promise<User[]> {return this.userService.getAll();}// post请求@Post('register')// 使用接收body数据 ,数据必须符合registerUserDto的结构create(@Body() registerUserDto: RegisterUserDto) {return this.userService.create(registerUserDto);}// 登录@Post('login')login(@Body() loginUserDto: LoginUserDto) {return this.userService.login(loginUserDto);}
}
预写dto
就是我们的数据传输对象,说白了就是ts的接口,规定了你的结构和规范
import { IsNotEmpty, MinLength } from 'class-validator';export class RegisterUserDto {@IsNotEmpty({ message: '用户名不能为空' })username: string;@IsNotEmpty({ message: '密码不能为空' })@MinLength(6, { message: '密码最少 4 位'})password: string;
}
import { IsNotEmpty, MinLength } from 'class-validator';export class LoginUserDto {@IsNotEmpty({ message: '用户名不能为空' })username: string;@IsNotEmpty({ message: '密码不能为空' })@MinLength(6, { message: '密码最少 4 位'})password: string;
}
编写服务
只有注入了实体,然后你才可以再service中使用他
这里的注册和登录我写了几个内容哈
第一是我们注册之前,要看看数据库中有没有数据,避免重复
第二,我们注册成功的时候,,我把密码加密处理放在数据库了,参考 bcrypt 包
第三,我们登录的时候,我不仅看看有没有存在用户,其次,我把密码进行加密和数据库中加密密码做了比对
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { RegisterUserDto } from './dto/register-user.dto';
import { LoginUserDto } from './dto/login-user.dto';// 信息加密包
import * as bcrypt from 'bcrypt';@Injectable()
export class UserService {constructor(// 使用仓库管理器方式,注入我们的数据库中的表@InjectRepository(User) private userRepository: Repository<User>) {}// 写一个查询用户是否存在的公共逻辑async findUser(registerUserDto: RegisterUserDto): Promise<boolean> {// 先查询用户是否存在const findUser = await this.userRepository.findOne({where: {username: registerUserDto.username,},});// 存在报错if (findUser) {throw new HttpException(`用户已存在了`, HttpStatus.CONFLICT);}return false;}// 查询所有用户getAll() {return this.userRepository.find();}// 创建用户async create(registerUserDto: RegisterUserDto) {// 看看存在不await this.findUser(registerUserDto);// 不存在的处理const user = new User();// 这里不用搞id,因为在实体里,id是自增量的主键user.username = registerUserDto.username;// user.password = registerUserDto.password;// 重新加密密码user.password = await bcrypt.hash(registerUserDto.password, 10);// 写入数据库await this.userRepository.save(user);return {status: HttpStatus.CREATED,message: '用户创建成功',user,};}async login(loginUserDto: LoginUserDto) {const user = await this.userRepository.findOne({where: {username: loginUserDto.username,},});// 用户不存在if (!user) {throw new HttpException(`用户不存在`, HttpStatus.NOT_FOUND);}// 校验密码// const isPasswordValid = user.password === loginUserDto.password;// if (!isPasswordValid) {// throw new HttpException(`密码错误`, HttpStatus.UNAUTHORIZED);// }// 解密密码,并且自动和login的密码校验了const bcryptPassWord = await bcrypt.compare(loginUserDto.password, user.password);if (!bcryptPassWord) {throw new HttpException(`密码错误`, HttpStatus.UNAUTHORIZED);}return {status: HttpStatus.OK,message: '登录成功',user,};}}
最后测试
查询所有用户
注册用户
测试登录
用户不存在
密码不对
登陆成功
当然了,其实一个用户模块不仅如此,后续你为了实现RBAC权限控制,你还要写很多东西,包括使用redis做缓存查询,等等等,当然了,我们操作数据库的方式,这是最简单的,不涉及任何的多表查询操作,事务等,因为你页用不了,你得用别的方式才可以更加灵活的深层次的操作数据库数据
慢慢看吧