0x0 Web 服务
因为开发前端的页面,需要展示单应用程序服务器,搜索 Nest.js
可以提供服务的地方,使用起来非常简单。安装依赖包:
yarn add @nestjs/serve-static
然后在 app.module.ts
配置启用:
import { join } from 'path'
import { ServeStaticModule } from '@nestjs/serve-static'
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'public'),
exclude: ['/api*']
})
对于 ServeStaticModule
配置选项非常简单参考:API Spec
rootPath
指定映射物理目录,exclude
排除的路径,fastify
服务不支持改选项,具体查阅文档。
0x1 守护进程
如果需要部署服务应用守护进程,安装全局依赖包:
yarn global add pm2
pm2 start PATH_TO_YOUR_PROJECT/dist/main.js --name=YOUR_APP_NAME
pm2 start YOUR_APP_NAME
pm2 restart YOUR_APP_NAME
pm2 stop YOUR_APP_NAME
0x3 日志系统
如果服务器系统出现错误情况,查找原因依然靠着日志文件,所以这样就需要打造一个完整的日志系统。在输出文件之前先需要把系统记录器完善。目前自带无法满足基本的需求,需要新建一个 LoggerService
业务来处理:
import { LoggerService as AppLoggerService, Injectable } from '@nestjs/common'
@Injectable()
export class LoggerService implements AppLoggerService {
log(message: string): void {
}
error(message: string, trace: string): void {
}
warn(message: string): void {
}
debug(message: string): void {
}
verbose(message: string): void {
}
}
然后在 LoggerModuel
导入:
import { Module } from '@nestjs/common'
import { LoggerService } from './logger.service'
@Module({
providers: [LoggerService],
exports: [LoggerService]
})
export class LoggerModule {}
因为程序实例化上下文之外,所以不参与正常依赖注入阶段,所以需要在下面进行实例:
const app = await NestFactory.create(AppModule, {
logger: false
})
app.useLogger(app.get(MyLogger))
await app.listen(3000)
不过上述方案有个缺点就是无法打印程序初始化的任何信息。所以就需要自定义日志系统,然后利用 log4js
存储日志记录。
yarn add log4js
改造 logger.service.ts
:
import { Logger as AppLogger } from '@nestjs/common'
import { Logger as log4jsLogger, configure, getLogger } from 'log4js'
export class LoggerService extends AppLogger {
log4js: log4jsLogger
constructor(logsDir: string) {
super()
configure({
appenders: {
all: {
filename: `${logsDir}/nestjs.services.log`,
type: 'dateFile',
// 配置 layout,此处使用自定义模式 pattern
layout: { type: 'pattern', pattern: '%d [%p] %m' },
// 日志文件按日期(天)切割
pattern: 'yyyy-MM-dd',
// 回滚旧的日志文件时,保证以 .log 结尾 (只有在 alwaysIncludePattern 为 false 生效)
keepFileExt: true,
// 输出的日志文件名是都始终包含 pattern 日期结尾
alwaysIncludePattern: true,
// 指定日志保留的天数
daysToKeep: 10
}
},
categories: { default: { appenders: ['all'], level: 'all' } }
})
this.log4js = getLogger()
}
log(message: any, trace: string) {
super.log(message, trace)
this.log4js.info(trace, message)
}
error(message: any, trace: string) {
super.error(message, trace)
this.log4js.error(trace, message)
}
warn(message: any, trace: string) {
super.warn(message, trace)
this.log4js.warn(trace, message)
}
debug(message: any, trace: string) {
super.debug(message, trace)
this.log4js.debug(trace, message)
}
verbose(message: any, trace: string) {
super.verbose(message, trace)
this.log4js.info(trace, message)
}
}
然后在 main.ts
替代默认的日志系统:
/*
* Copyright (c) 2021 Jaxson
* 项目名称:Vue-Admin-Plus-Nestjs-Api
* 文件名称:main.ts
* 创建日期:2021年03月27日
* 创建作者:Jaxson
*/
import { NestFactory } from '@nestjs/core'
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'
import { ConfigService } from '@nestjs/config'
import { AppModule } from '@/app.module'
import { LoggerService } from '@/logger/logger.service'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// 获取全局配置
const configService = app.get<ConfigService>(ConfigService)
const logsDir = configService.get<string>('logsDir')
const logger = new LoggerService(logsDir)
app.useLogger(logger)
app.setGlobalPrefix('api')
app.enableCors() // 启用允许跨域
const config = new DocumentBuilder()
.setTitle('Vue Admin Plus 管理系统接口文档')
.setDescription('这是一份关于 Vue Admin Plus 管理系统的接口文档')
.setVersion('1.0.0')
.addBearerAuth()
.build()
const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('docs', app, document)
await app.listen(configService.get<number>('port'))
logger.log(`设置应用程序端口号:${configService.get<number>('port')}`, 'bootstrap')
logger.log(`应用程序接口地址: http://localhost:${configService.get<number>('port')}/api`, 'bootstrap')
logger.log(`应用程序文档地址: http://localhost:${configService.get<number>('port')}/docs`, 'bootstrap')
logger.log('🚀 服务应用已经成功启动!', 'bootstrap')
}
bootstrap()
同样在请求接口和异常过滤器替代自带日志记录:
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'
import { Response, Request } from 'express'
import { LoggerService } from '@/logger/logger.service'
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private logger: LoggerService) {}
catch(exception: HttpException, host: ArgumentsHost) {
const context = host.switchToHttp()
const response = context.getResponse<Response>()
const request = context.getRequest<Request>()
const status = exception.getStatus()
const message = exception.getResponse()['message']
this.logger.log(`${request.url} - ${message}`, '非正常接口请求')
response.status(status).json({
statusCode: status,
message: message,
path: request.url,
timestamp: new Date().toISOString()
})
}
}
/*
* Copyright (c) 2021 Jaxson
* 项目名称:Vue-Admin-Plus-Nestjs-Api
* 文件名称:transform.interceptor.ts
* 创建日期:2021年03月27日
* 创建作者:Jaxson
*/
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Request } from 'express'
import { LoggerService } from '@/logger/logger.service'
interface Response<T> {
data: T
}
interface HasTokenUserEntity extends Express.User {
token?: string
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
constructor(private logger: LoggerService) {}
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
const request = context.switchToHttp().getRequest<Request>()
const user: HasTokenUserEntity = request.user
this.logger.log(request.url, '正常接口请求')
return next.handle().pipe(
map(data => {
const result = data
// 判断接口是否更新 Token
if (user.token) result['token'] = user.token
return {
data: result,
statusCode: 200,
message: '请求成功'
}
})
)
}
}
大概就这样,不过目前还是有点粗糙,日志类别分的不是很清楚,不过目前对 log4js
不是太熟悉,后期熟悉再慢慢改造。