Nestjs+ts实践如何接入aliyun-sdk

627 阅读3分钟

大家好,今天讲一讲最近自己的nestjs+ts项目接入aliyun-sdk做日志上报的实践。

背景

由于我们整个项目架构引入了第三方日志系统SLS,所以各个服务都得改造。 查阅 官方文档官方demo 结合我们当前场景只需要考虑putLogs的情况,node-sdk使用会十分简单。

接入项目

Nest框架的核心思想是什么?依赖注入AOP

所以思路就是将我们的接入和上报封装成一个SLSModule,通过配置依赖的方式注入到appModule,然后在切面中使用SLSModule。

创建module

// ali-sls/service.ts

import { Injectable, Logger } from '@nestjs/common'
import { GenerateSLSDto, projectName, registerConfig, shouldPutLogRoutes, storeName } from './helper'
import { SlsVo } from './sls.dto'
import { SLS } from 'aliyun-sdk'

@Injectable()
export class SlsService {
  private slsServer: any
  constructor() {
    this.slsServer = new SLS(registerConfig)
  }

  putLogs(slsVo: SlsVo): void {
    // 过滤不需要上报日志的接口路径
    if (!Object.keys(shouldPutLogRoutes).includes(slsVo.requestUri)) return

    this.slsServer.putLogs(
      {
        projectName: projectName,
        logStoreName: storeName,
        logGroup: {
          logs: [GenerateSLSDto(slsVo)]
        }
      },
      function (err) {
        Logger.log('SLS上报成功!')
        if (err) {
          Logger.error('SLS上报失败:' + err)
          return
        }
      }
    )
  }
}

我们通过在创建service实例的构造函数中实例化SLS对象,然后在appModule中imports这个模块。

切面上报

整理出来我的上报需求:

  • 针对部分接口上报
    • 执行成功上报成功提示
    • 执行失败上报异常内容

具体到代码实现,是在拦截器和异常过滤器中分别上报成功和失败的日志。

// resInterceptor.ts
// 拦截器原先职责是统计接口耗时以及封装响应参数格式
// 新添加一段上报任务
@Injectable()
export class ResInterceptor<T> implements NestInterceptor<T, Response<T>> {
  // 注入了我们的SlsService服务
  constructor(private slsService: SlsService) {}
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    const now = Date.now()
    const ctx = context.switchToHttp()
    const request = ctx.getRequest()
    Logger.warn(`调用 ${request.url}开始`)
    return next.handle().pipe(
      map(data => {
        Logger.debug(`调用 ${request.url} 结束: ${Date.now() - now}ms`)
        // 上报
        this.slsService.putLogs({
          logData: '',
          requestUri: request.url,
          jobStatus: 'success',
          logLevel: 'info'
        })
        if (request.url.startsWith('/fsEvent')) {
          return data
        } else {
          return { data: data, code: 200, msg: 'success' }
        }
      })
    )
  }
}

// AllExceptionsFilter.ts
// 全局捕获处理异常类,抛出的任意异常都在这里处理
// 在这里统一封装了异常的返回内容和格式

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'
import { BaseExceptionFilter } from '@nestjs/core'
import { SlsService } from '@/ali-sls/sls.service'

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter implements ExceptionFilter {
  // 注入我们的SlsService服务
  constructor(private slsService: SlsService) {
    super()
  }

  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse()
    const request = ctx.getRequest()

    const isHttpErr = exception instanceof HttpException
    isHttpErr ? Logger.error(JSON.stringify(exception, null, 2)) : Logger.error(exception)
    const status = isHttpErr ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
    response.status(status).json({
      code: status,
      timestamp: new Date().toLocaleDateString(),
      path: request.url,
      message: exception.response || '内部异常'
    })
    // 上报
    this.slsService.putLogs({
      logData: exception.response ? JSON.stringify(exception.response) : '内部异常',
      requestUri: request.url,
      jobStatus: 'fail',
      logLevel: 'error'
    })
  }
}

由于异常类是全局注册的,需要手动将实例传入。

// main.ts
const slsService = app.get<SlsService>(SlsService)
app.useGlobalFilters(new AllExceptionsFilter(slsService))

思路就是全局拦截并调用putLogs方法,在方法内部去过滤有效的路由。在切面中写的优势在于业务代码侵入性小,也意味着工作量小,后续扩展简单方便

解决ts报错

由于没有找到sdk的.d.ts文件,ts编译会报错。

// /types/aliyun-sdk.d.ts
declare module 'aliyun-sdk' {
  export class SLS {
    constructor(config: Record<string, string>) {}
  }
}
"typeRoots": ["node_modules/@types", "types"]

可以如上通过手动定义模块来解决这个报错。

总结

实现的效果其实和官方demo用express是一样的,只是不同的框架思想不同,写法不同。

Nest这套思想其实和JAVA的Spring那套基本是一样的~~

最后,感谢阅读!