这里开始,我将用NestJS框架做后端,Vben框架做前端框架,使用Ant Design Vue UI框架做一个简单的后台系统。
一、Vben前端项目准备
1、下载框架源码
文档地址:Vben Admin
代码地址:https://github.com/vbenjs/vue-vben-admin
2、精简项目
Vben框架本身集成了演示项目、文档项目、ElementPlus项目、Ant Design Vue项目、NativeUI项目、还有一个mock项目。我只留了Ant Design Vue项目,其余全部删除。
3、调整接口
可以根据这个文档,一步一步替换修改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
搭建成功,接下来我们就可以添加业务功能了。