本文旨在记录使用 Node.js 的企业级应用开发框架 NestJs 的快速构建流程,以便日后能够快速进行开发。
1.使用脚手架构建项目
1.npm i -g @nestjs/cli 或 pnpm add -g @nestjs/cli安装脚手架。
2.nest new project-name --strict使用脚手架创建一个名为“project-name”的项目。
2.项目结构
src/common目录下有decorator、fliter、guard、interceptor、utils等目录。用于存放装饰器、过滤器、守卫、拦截器和一些常用方法等。
src/modules目录下存放项目所用到的业务模块。
.env存放一些配置常量。
3.统一返回响应结果
使用拦截器将返回给前端的数据格式化为标准统一的数据,方便前端对接口数据类型的统一编写。
// src/common/interceptor/response.ts
import {NestInterceptor, CallHandler, ExecutionContext, Injectable } from '@nestjs/common'
import { map } from 'rxjs/operators'
import {Observable} from 'rxjs'
interface Response<T> {
data: T
}
@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler<T>):Observable<Response<T>> {
return next.handle().pipe(map(data=> {
return {
data,
code:200,
message:"success",
ok:true
}
}))
}
}
在main.js中注册为全局的响应拦截器。
// main.js
import { ResponseInterceptor } from 'src/common/interceptor/response';
app.useGlobalInterceptors(new ResponseInterceptor()) // 注册响应拦截器
4.捕获项目异常
定义异常过滤器对项目中的异常进行捕获,并返回统一的响应结果。
// src/common/fliter/exception.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, } from '@nestjs/common';
import { Response } from 'express';
@Catch() // 捕获所有类型的异常
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const res = ctx.getResponse<Response>();
// 如果是http异常,则返回对应状态和错误提示。其它错误则返回500服务器错误
const httpStatus = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
res.status(httpStatus).json({ // 统一响应结果
data: exception.message || '服务器错误',
code: httpStatus,
message: "error",
ok: false
});
}
}
在main.js中注册为全局的异常过滤器。
// main.js
import { HttpExceptionFilter } from 'src/common/fliter/exception';
app.useGlobalFilters(new HttpExceptionFilter()); // 注册异常过滤器
5.全局模块配置
在应用根模块中配置常用的模块。
安装所需依赖:pnpm i mysql2 ioredis typeorm @nestjs/config @nestjs/jwt @nestjs/typeorm @nestjs-modules/ioredis
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";
import { TypeOrmModule } from "@nestjs/typeorm";
import { RedisModule } from '@nestjs-modules/ioredis';
// 环境变量
const envFilePath = ['production.env'];
if (process.env.NODE_ENV === 'development') { // 开发环境使用development.env变量
envFilePath.unshift('development.env')
}
@Module({
imports: [
// .env 模块配置
ConfigModule.forRoot({
isGlobal: true,
envFilePath
}),
// 数据库模块配置
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get<string>('DATABASE_HOST', '127.0.0.1'),
port: +configService.get<string>('DATABASE_PORT','3306'),
username: configService.get<string>('DATABASE_USERNAME', 'root'),
password: configService.get<string>('DATABASE_PASSWORD', '123456'),
database: configService.get<string>('DATABASE_NAME', 'root'),
// synchronize: true, // 生产环境下不要打开 否则会造成数据的丢失
autoLoadEntities: true,
}),
inject: [ConfigService]
}),
// Redis 模块配置
RedisModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
type: 'single', // 单例
options:{
host: configService.get<string>('REDIS_HOST','127.0.0.1'),
port: +configService.get<string>('REDIS_PORT','6379'),
db:+configService.get<string>('REDIS_DB','0')
}
}),
inject:[ConfigService]
}),
// jwt 模块配置
JwtModule.registerAsync({
global: true,
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET', 'luoaoxuan'),
signOptions: {
expiresIn: configService.get<string>('JWT_EXPIRES_IN', '2h'),
},
}),
inject: [ConfigService]
}),
// 其他模块
],
controllers: [AppController],
providers: [AppService],
exports: [JwtModule]
})
export class AppModule { }
在.env文件中设置环境常量样例。
// .env # 数据库配置 DATABASE_HOST=127.0.0.1 DATABASE_PORT=3306 DATABASE_USERNAME=root DATABASE_PASSWORD=root DATABASE_NAME=mx_db # Redis 配置 REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_DB=1 # jwt配置 JWT_SECRET= JWT_EXPIRES_IN= # 其他配置
6.验证身份
定义身份验证的守卫,检查token的合法性。
// src/guard/AuthGuard.ts
import { CanActivate, ExecutionContext, HttpException, Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Request } from "express";
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) { }
getToken(req: Request) { // 获取token的方法 这里不唯一
return req.headers.authorization
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.getToken(request);
if (!token) {
throw new HttpException('token无效', 401);
}
try {
const payload = await this.jwtService.verifyAsync(token)
request.userInfo = payload; // 将token中解析出来的用户信息存入request对象中
} catch {
throw new HttpException('token无效', 401);
}
return true;
}
}
由于我们在解析token后将用户信息存入request对象中,因此可以自定义一个装饰器用于获取用户信息。
// src/common/decorator/UserInfo.ts
import { ExecutionContext, createParamDecorator } from "@nestjs/common"
export const UserInfo = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data?request.userInfo[data]:request.userInfo // 如果没有传参则返回整个对象,如果传参则返回对应的字段值
}
)
在controller中简单的使用:
@UseGuards(AuthGuard) // 使用守卫验证token的合法性
@Delete('/userUpdate')
remove(@UserInfo('id') id: string) { // 使用自定义装饰器获取token解析出来的用户信息
return this.userService.remove(id)
}
其次,还可以利用守卫完成一些如验签、限流等操作。
7.常用的工具函数封装
安装所需依赖:pnpm i moment
// src/common/utils/utils.ts
import * as moment from "moment";
import * as crypto from "crypto";
// 获取当前时间|格式化时间
export const getTime = (time:number|Date = new Date().getTime(), rule : string = "YYYY-MM-DD HH:mm:ss") : string => {
return moment(time).format(rule);
}
// 普通不可逆加密
export const hashEncode = (data: string, type: 'md5' | 'sha1') => {
let hash = crypto.createHash(type);
hash.update(data);
return hash.digest('hex');
}
// 不可逆加密 需要key
export const hmacEncode = (data: string, key: string, type: 'md5' | 'sha1' | 'sha256') => {
let hmac = crypto.createHmac(type, key);
hmac.update(data);
return hmac.digest('hex');
}
// 可逆加密 需要key和iv
export const encode = (data: string, key: string, iv: string) => {
let cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
return cipher.update(data, 'binary', 'hex') + cipher.final('hex');
}
// 可逆加密的解密 需要key和iv
export const decode = (data: string, key: string, iv: string) => {
let encipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
data = Buffer.from(data, 'hex').toString('binary');
return encipher.update(data, 'binary', 'utf-8') + encipher.final('utf8');
}
// 生成指定长度的随机串
export const generateRandomString = (length: number) => {
return crypto.randomBytes(Math.ceil(length / 2))
.toString('hex') // 将随机字节转换为十六进制字符串
.slice(0, length); // 保证字符串长度为指定的长度
}
8.云服务器中运行
pm2 start npm --name 'name' -- run start:prod
–name后面跟的是项目命名。
— 后面跟的是package.json文件中的启动命令。






