网站LOGO
白雾茫茫丶
页面加载中
11月21日
网站LOGO 白雾茫茫丶
记录学习、生活和有趣的事
菜单
  • 白雾茫茫丶
    记录学习、生活和有趣的事
    用户的头像
    首次访问
    上次留言
    累计留言
    我的等级
    我的角色
    打赏二维码
    打赏博主
    Nest.js 实战 (九):使用拦截器记录用户 CURD 操作日志
    点击复制本页信息
    微信扫一扫
    文章二维码
    文章图片 文章标题
    创建时间
  • 一 言
    确认删除此评论么? 确认
  • 本弹窗介绍内容来自,本网站不对其中内容负责。
    • 复制图片
    • 复制图片地址
    • 百度识图
    按住ctrl可打开默认菜单

    Nest.js 实战 (九):使用拦截器记录用户 CURD 操作日志

    谢明伟 · 原创 ·
    前端开发Nest 实战 · TypeScriptNest
    共 4602 字 · 约 2 分钟 · 489
    本文最后更新于2024年08月13日,已经过了99天没有更新,若内容或图片失效,请留言反馈

    前言

    有一天,公司的产品经理提了一个需求:系统需要记录每个用户的 CURD 操作,也就是说用户新增、编辑或者删除了什么数据,都需要记录下来,这个在 Nest.js 中如何实现呢?

    这时候我们可以考虑使用 拦截器 来实现。

    什么是拦截器?

    拦截器 是使用 @Injectable() 装饰器注解的类。拦截器应该实现 NestInterceptor 接口。

    拦截器 具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:

    • 在函数执行之前/之后绑定额外的逻辑
    • 转换从函数返回的结果
    • 转换从函数抛出的异常
    • 扩展基本函数行为
    • 根据所选条件完全重写函数 (例如, 缓存目的)

    创建 Prisma 模型

    schema.prisma 文件中添加 Log 模型:

    txt 代码:
    // Log - method
    enum Method {
      GET
      POST
      PATCH
      DELETE
    }
    
    // 系统管理 - 操作日志
    model Log {
      id            String          @id @default(uuid()) // 主键
      userId        String          // 关联的用户 id
      user          User            @relation(fields: [userId], references: [id])
      ip            String          // ip
      action        String          // 请求地址
      method        Method          // 请求方法
      params        Json            // 请求参数(JSON 对象)
      os            String          // 系统
      browser       String          // 浏览器
      createdAt     DateTime        @default(now()) // 创建时间
    }

    这里可以根据自己实际需求调整。

    创建 Module 模块

    这里我们需要用到 Session 保存的用户数据,但 Service 中是不能直接获取 Session 的,我们需要注入作用域,以此来获取请求中的上下文。

    新建 operation-log.service.ts 文件:

    ts 代码:
    import { Inject, Injectable, Scope } from '@nestjs/common';
    import { REQUEST } from '@nestjs/core';
    import { Request } from 'express';
    import UAParser from 'ua-parser-js';
    
    import { PrismaService } from '@/modules/prisma/prisma.service';
    
    
    @Injectable({ scope: Scope.REQUEST })
    export class OperationLogService {
      constructor(
        @Inject(REQUEST)
        private readonly request: Request & { session: Api.Common.SessionInfo },
        private prisma: PrismaService,
      ) 
    
      /**
       * @description: 录入日志
       */
      async logAction() {
        const { originalUrl, method, headers, ip, body, query } = this.request;
        const userAgent = headers['user-agent'];
        const parser = new UAParser(userAgent);
        let { userInfo } = this.request.session;
        // 登录接口需要单独处理
        const isLogin = originalUrl === '/auth/login';
        if ((userInfo && method.toUpperCase() !== 'GET') || isLogin) {
          if (isLogin) {
            // 查询数据库中对应的用户
            userInfo = await this.prisma.user.findUnique({
              where: { userName: body.userName },
            });
          }
          const data: any = {
            userId: userInfo.id,
            action: originalUrl,
            method: method.toUpperCase(),
            ip,
            params: { ...body, ...query },
            os: Object.values(parser.getOS()).join(' '),
            browser: parser.getBrowser().name,
          };
          // 插入数据到表
          await this.prisma.log.create({
            data,
          });
        }
      }
    }

    因为登录接口此时 Session 还没有保存用户数据,我们需要单独处理一下,这里我们只记录非 GET 请求的路由。

    创建 LoggerInterceptor 拦截器

    新建 interceptor/logger.interceptor.ts 文件,写入:

    ts 代码:
    import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    import { OperationLogService } from '@/modules/system-manage/operation-log/operation-log.service';
    
    @Injectable()
    export class LoggerInterceptor implements NestInterceptor {
      constructor(private readonly operationLogService: OperationLogService) {}
    
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        this.operationLogService.logAction();
        return next.handle().pipe(map((data) => data));
      }
    }

    绑定拦截器

    在需要绑定的 Controller 中使用 @UseInterceptors() 装饰器,与守卫一样, 拦截器可以是控制器范围内的, 方法范围内的或者全局范围内的。

    ts 代码:
    import { UseInterceptors } from '@nestjs/common';
    import { LoggerInterceptor } from '@/interceptor/logger.interceptor';
    
    @UseInterceptors(LoggingInterceptor)
    export class UserManageController {}

    在绑定拦截器后,用户每次调用 Controller 中的路由处理程序都将使用 LoggingInterceptor,也就是说会把用户的操作等信息记录到表中。

    效果演示

    总结

    这个功能本来一开始我是想使用 中间件 来开发的,后来不管怎么折腾,中间件Request 上下文始终获取不到 Session,但 拦截器 也不失是一种好方法。

    Github 仓库OperationLogService

    声明:本文由 谢明伟(博主)原创,依据 CC-BY-NC-SA 4.0 许可协议 授权,转载请注明出处。

    还没有人喜爱这篇文章呢

    我要发表评论 我要发表评论
    博客logo 白雾茫茫丶 记录学习、生活和有趣的事 51统计 百度统计
    MOEICP 萌ICP备20236860号 ICP 粤ICP备2023007649号 ICP 粤公网安备44030402006402号

    💻️ 谢明伟 33分钟前 在线

    🕛

    本站已运行 2 年 325 天 2 小时 49 分

    🌳

    自豪地使用 Typecho 建站,并搭配 MyLife 主题
    白雾茫茫丶. © 2022 ~ 2024.
    网站logo

    白雾茫茫丶 记录学习、生活和有趣的事