Node后台学习笔记-毕设篇-第四章 用户登录实现

74 阅读3分钟

Node 后台学习笔记-毕设篇(旅游信息管理平台)

  • 使用技术栈:Node + ts + nestjs + typeorm + MySQL

  • 简介:

    • 这是款具有上传下载、发布文章、评论、用户个人中心 的一款 旅游资源管理项目。
    • 可对数据库进行操作
  • 项目链接: github.com/donwenx

一、实现 token 封装

新建一个 token 模块

nest g resource token

新建一个文件放 token 实体

// token.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Token {
  @PrimaryGeneratedColumn()
  id: number;

  @Column("int")
  userId: number;

  @Column("varchar")
  token: string;

  @Column("int")
  state: number;

  @Column("int")
  loginTime: number;

  @Column("int")
  expire: number;
}

二、优化注册功能

前面实现的注册功能要优化一下功能

  • 解决用户名 重复注册

Service 层中设置一个查找用户name的方法

通过repository.findOneBy()查找具有该 name 的实体

// user.service.ts
/**
 * 通过name获取用户信息
 * @param name 用户名
 * @returns 用户信息
 */
async getUserByName(name: string) {
  const user = await this.repository.findOneBy({ name })
  return user
}

调用 service 中的根据 name 查找用户的方法,用户存在抛出错误

// user.controller.ts
@Post('/create')
async createUser(@Body() createUserDto: CreateUserDto) {
  const user = await this.userService.getUserByName(createUserDto.name)
  if (user !== null) { // 用户存在抛出错误
    return '用户名重复'
  }
  const data = await this.userService.createUser(
    createUserDto.name,
    createUserDto.password
  )
  return user
}

三、登录功能实现

新建 token 模块用于存放用户登录过期时间信息

注册实体

// token.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Token {
  @PrimaryGeneratedColumn()
  id: number;

  @Column("int")
  userId: number;

  @Column("varchar")
  token: string;

  @Column("int")
  state: number;

  @Column("int")
  loginTime: number;

  @Column("int")
  expire: number;
}

实现 service 逻辑

// token.service.ts
import { Injectable } from '@nestjs/common';
import { Token } from './token.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { createNonceStr, getTimeStamp } from 'src/util/function';

@Injectable()
export class TokenService {

  @InjectRepository(Token)
  private repository: Repository<Token> // 注入一个token实体

  /**
   * 根据用户的userId创建token
   * @param userId 用户的userId
   * @param expire 过期时间
   * @returns token信息
   */
  async createToken(userId: number, expire: number) {
    const data = new Token()
    data.userId = userId
    data.loginTime = getTimeStamp()
    data.expire = data.loginTime + expire
    data.token = createNonceStr(32)
    data.state = 1
    await this.repository.save(data)
    return data.token
  }
}

在导出 token

// token.module.ts
import { Module } from "@nestjs/common";
import { TokenService } from "./token.service";
import { TokenController } from "./token.controller";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Token } from "./token.entity";

@Module({
  imports: [TypeOrmModule.forFeature([Token])],
  controllers: [TokenController],
  providers: [TokenService],
  // 导出 user 给其他的模块使用
  exports: [TypeOrmModule.forFeature([Token])],
  // 方案1:
  // exports:[TypeOrmModule.forFeature([Token])]
})
export class TokenModule {}

在 user 模块中导入 token

// user.module.ts
import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { TokenService } from "src/token/token.service";
import { TokenModule } from "src/token/token.module";
import { Token } from "src/token/token.entity";

@Module({
  // 方案2
  // imports: [TypeOrmModule.forFeature([User, Token]), TokenModule],
  imports: [TypeOrmModule.forFeature([User]), TokenModule],
  controllers: [UserController],
  providers: [UserService, TokenService],
  exports: [UserService, TokenService],
})
export class UserModule {}

user.controller.ts 注入 TokenService 将在后面登录接口中使用创建 token

// user.controller.ts
  @Inject()
  private readonly tokenService: TokenService;

user.service.ts 中实现一个检测密码方法

// user.service.ts

  /**
   * 检查用户密码是否正确
   * @param name 用户名
   * @param password 密码
   * @returns 返回true或false
   */
  async checkPassword(name: string, password: string) {
    // 通过从数据库中查询用户,判断数据库中的密码和当前输入的密码一致
    const user = await this.repository.findOneBy({ name })
    if (user.password === password) {
      return true
    }
    return false
  }

正式实现登录接口,返回 token 和用户信息

// user.controller.ts
@Post('/login')
async login(@Body() loginDto: LoginDto) {
  // 1. 拿到 id
  //  在 user controller 中实现一个新的接口,登录接口`/login`
  const user = await this.userService.getUserByName(loginDto.name)
  // 2. 判断用户是否存在
  if (user === null) {
    return '用户不存在'
  }
  // 3. 使用 id + password 检查密码
  const ok = await this.userService.checkPassword(user.name, loginDto.password)
  // 判断密码是否正确
  if (!ok) {
    // 密码错误抛出异常
    return '密码错误'
  }
  // 设置token
  const token = await this.tokenService.createToken(user.id, 24 * 60 * 60)

  const { password, ...data } = user // 移除密码

  return { token, ...data }
}

测试

登录成功测试

01.png

登录失败测试

02.png

用户不存在测试

03.png

目录