NestJS+Vben学习记录-项目搭建

        这里开始,我将用NestJS框架做后端,Vben框架做前端框架,使用Ant Design Vue UI框架做一个简单的后台系统。

一、Vben前端项目准备

1、下载框架源码

文档地址:Vben Admin

代码地址:https://github.com/vbenjs/vue-vben-admin

2、精简项目

文档地址:精简版本 | Vben AdminVben Admin & 企业级管理系统框架icon-default.png?t=O83Ahttps://doc.vben.pro/guide/introduction/thin.html

Vben框架本身集成了演示项目、文档项目、ElementPlus项目、Ant Design Vue项目、NativeUI项目、还有一个mock项目。我只留了Ant Design Vue项目,其余全部删除。

3、调整接口

文档地址:登录 | Vben AdminVben Admin & 企业级管理系统框架icon-default.png?t=O83Ahttps://doc.vben.pro/guide/in-depth/login.html

可以根据这个文档,一步一步替换修改NestJS后台接口。

1)后端接口调整

        Vben有刷新token的机制,之前我们Jwt认证时,并没有提供refreshToken,并且考虑到我们都登出功能,但是JwtService并没有提供销毁token的机制,只能等待accessToken和refreshToken自动过期,所有我们这里登录时,会把token放入缓存中。退出登录时,从缓存中把token key删除,在authguard中,再检测缓存中是否有这个token。如果没有,说明token删除,返回401,重新登录。

新建一个DTO,存放返回的accessToken和refreshToken

export class TokenDto {
  accessToken: string
  refreshToken: string
}

在auth.service.ts中更新AuthService的登录代码,并添加一个刷新token方法和一个登出的方法。代码最终如下:

import { BadRequestException, HttpException, Injectable, Req, UnauthorizedException, UseInterceptors } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { I18nService } from 'nestjs-i18n';
import { CacheManagerService } from 'src/cachemanager/cachemanager.service';
import { I18nTranslations } from 'src/generated/i18n.generated';
import { UserService } from 'src/user/user.service';
import { CryptoService } from 'src/utils/crypto.service';
import { TokenDto } from './dto/auth.login.dto';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AuthService {
  private TOKEN_PREFIX: string
  constructor(
    private readonly userService: UserService,
    private readonly jwtService: JwtService,
    private readonly cryptoService: CryptoService,
    private readonly i18n: I18nService<I18nTranslations>,
    private readonly cacheService: CacheManagerService,
    private readonly configService: ConfigService
  ) {
    this.TOKEN_PREFIX = this.configService.get<string>("REDIS_TOKEN_PREFIX")
  }

  async signIn(
    account: string,
    password: string,
    captcha: boolean
  ): Promise<any> {
    //先固定,后面再处理
    if (captcha !== true)
      throw new BadRequestException(this.i18n.t('user.LoginValid.CAPTCHA_ERROR'));

    const user = await this.userService.findOneByAccount(account);
    if (user == null || user == undefined)
      throw new BadRequestException(this.i18n.t('user.LoginValid.ACCOUNT_ERROR'));

    if (await this.cryptoService.decryption(user.password) !== password)
      throw new BadRequestException(this.i18n.t('user.LoginValid.PASSWORD_ERROR'));

    const payload = { sub: user.id, account: user.account };
    const data: TokenDto = {
      accessToken: await this.jwtService.signAsync(payload),
      refreshToken: await this.jwtService.signAsync(payload, { expiresIn: '15d' })
    }
    this.cacheService.set(`${this.TOKEN_PREFIX}_${user.id}_accesstoken`, data.accessToken)
    this.cacheService.set(`${this.TOKEN_PREFIX}_${user.id}_refreshtoken`, data.refreshToken)
    return data;
  }

  async refreshToken(refreshToken: string): Promise<string> {
    const { sub } = this.jwtService.verify(refreshToken);
    if (sub == null || sub == '')
      return ''
    const user = await this.userService.findOne(sub);
    if (user) {
      const payload = { sub: user.id, account: user.account };
      let accesstoken = await this.jwtService.signAsync(payload)
      this.cacheService.set(`${this.TOKEN_PREFIX}_${user.id}_accesstoken`, accesstoken)
    }
    return ''
  }

  async logout(userId: number) {
    this.cacheService.del(`${this.TOKEN_PREFIX}_${userId}_accesstoken`)
    this.cacheService.del(`${this.TOKEN_PREFIX}_${userId}_refreshtoken`)
  }
}

 根据AuthService修改,修改AuthController代码如下:

import {
  Body,
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Post,
  Req,
  Request,
  UseGuards
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { Public } from './public.decorator';
import { LoginInDto } from './dto/auth.login.dto';
import { ApiOperation } from '@nestjs/swagger';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Public()
  @HttpCode(HttpStatus.OK)
  @Post('login')
  @ApiOperation({ summary: '登录' })
  signIn(@Body() signInDto: LoginInDto) {
    return this.authService.signIn(signInDto.username, signInDto.password, signInDto.captcha);
  }

  @Get('info')
  @ApiOperation({ summary: '获取当前登录人' })
  getProfile(@Request() req) {
    return req.user;
  }

  @Public()
  @Get('refresh')
  @ApiOperation({ summary: '刷新TOKEN' })
  refreshToken(@Request() req) {
    const refreshToken = req.headers['refresh_token'] || '';
    return this.authService.refreshToken(refreshToken as string);
  }

  @Get('logout')
  async logout(@Request() req) {
    this.authService.logout(req.user.id);
  }

  @Get('codes')
  codes(@Request() req) {
    console.log(req.ip)
    return []
  }
}

 至此我们已经按照Vben文档,提供了登录、当前登录人信息、刷新token、登出、获取角色集合(目前没有角色表,先返回了空数组)

2、Vben代码调整

1)按照拦截器统一返回格式,调整Vben接收的数据格式,修改HttpResponse

packages\effects\request\src\request-client\types.ts文件中

interface HttpResponse<T = any> {
  /**
   * 200 表示成功 其他表示失败
   * 200 means success, others means fail
   */
  code: number;
  data: T;
  error: string;
  message: string;
  timestamp: string;
}

2)apps\web-antd\src\api\request.ts文件调整

按照拦截器统一返回格式,调整拦截器:

请求拦截器修改为(这里把我们i18n国际化头部key传入,因为NestJS i18n国际化创建的时候跟Vben设置的语言包(zh-CN、en-US)一致,这里不用修改):

  client.addRequestInterceptor({
    fulfilled: async (config) => {
      const accessStore = useAccessStore();
      config.headers.Authorization = formatToken(accessStore.accessToken);
      // config.headers['Accept-Language'] = preferences.app.locale;
      config.headers['x-lang'] = preferences.app.locale;
      return config;
    },
  });

返回拦截器修改为

 我们先默认,code不等于200,就提示错误

  // response数据解构
  client.addResponseInterceptor<HttpResponse>({
    fulfilled: (response) => {
      const { data: responseData } = response;

      const { code, data } = responseData;
      // if (status >= 200 && status < 400 && code === 0) {
      //   return data;
      // }
      if (code === 200) return data;

      throw Object.assign({}, response, { response });
    },
  });

3)修改apps\web-antd\src\api\core\user.ts中info接口的地址,我们这里是auth/info 

export async function getUserInfoApi() {
  return requestClient.get<UserInfo>('/auth/info');
}

4)修改 apps\web-antd\src\api\core\auth.ts中logoutApi和getAccessCodesApi,先让接口都用requestClient请求

/**
 * 退出登录
 */
export async function logoutApi() {
  return requestClient.get('/auth/logout', {
    withCredentials: true,
  });
}

/**
 * 获取用户权限码
 */
export async function getAccessCodesApi() {
  return requestClient.get<string[]>('/auth/codes');
}

4、配置Vben项目配置文件和请求代理

在apps\web-antd\.env\.env.development中修改

VITE_NITRO_MOCK=false

 在apps\web-antd\vite.config.mts中修改target为我们自己后端的地址

 target: 'http://localhost:3000',

5、运行、测试

启动后端和前端项目

1)国际化

 这里说明我们后端国际化起作用了

2)接口登录

我把Vben启动端口改成了6789.

登录请求:http://localhost:6789/api/auth/login 会自动代理到 http://localhost:3000/auth/login

 搭建成功,接下来我们就可以添加业务功能了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值