当前位置: 首页 > news >正文

npm包版本不一致的问题

ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(自我控制是最强的本能 - 萧伯纳)
ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ在这里插入图片描述

问题

@albedo-auth-nestjs包(0.2.8版本之前)在安装到项目中后,在使用中无法抛出自定义的异常错误,而在以文件模块形式在项目中使用则能够正常鉴权和抛出异常错误。
根据官方文档描述,默认会有一个全局异常过滤器,对于HttpException或者继承自HttpException类的异常,Nestjs默认会返回一个JSON 异常响应:

{"statusCode":500,"message":"Internal server error"}

在这里插入图片描述

而在鉴权守卫代码中使用的异常类是UnauthorizedException,该类继承了HttpException类。那么按照官方定义,是可以抛出UnauthorizedException异常的

throw new UnauthorizedException(this.verbose && 'Authorization header is required')
export class UnauthorizedException extends HttpException {
// .......
}

但经过测试,发现无论抛出什么异常,最终总是返回

{"statusCode":500,"message":"Internal server error"}

排查

于是开始调试源码,了解了Nestjs异常过滤器的处理流程

  1. Nestjs应用程序启动时会给Controller路由层的每一个方法绑定回调函数,该回调函数内包含了守卫,管道,拦截器等。用于后续每次请求执行
create(instance, callback, methodName, moduleKey, requestMethod, contextId = constants_3.STATIC_CONTEXT, inquirerId) {
  const contextType = 'http';
  const { argsLength, fnHandleResponse, paramtypes, getParamsMetadata, httpStatusCode, responseHeaders, hasCustomHeaders, } = this.getMetadata(instance, callback, methodName, moduleKey, requestMethod, contextType);
  const paramsOptions = this.contextUtils.mergeParamsMetatypes(getParamsMetadata(moduleKey, contextId, inquirerId), paramtypes);
  // 管道
  const pipes = this.pipesContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
  // 守卫
  const guards = this.guardsContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
  // 拦截器
  const interceptors = this.interceptorsContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
  const fnCanActivate = this.createGuardsFn(guards, instance, callback, contextType);
  const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
  const handler = (args, req, res, next) => async () => {
    fnApplyPipes && (await fnApplyPipes(args, req, res, next));
    return callback.apply(instance, args);
  };
  // 回调函数
  return async (req, res, next) => {
    const args = this.contextUtils.createNullArray(argsLength);
    fnCanActivate && (await fnCanActivate([req, res, next]));
    this.responseController.setStatus(res, httpStatusCode);
    hasCustomHeaders &&
      this.responseController.setHeaders(res, responseHeaders);
    const result = await this.interceptorsConsumer.intercept(interceptors, [req, res, next], instance, callback, handler(args, req, res, next), contextType);
    await fnHandleResponse(result, res, req);
  };
}

在这里插入图片描述

  1. 每一个回调函数都被包裹在try catch块中。用于处理全局异常,执行next函数
class RouterProxy {
    createProxy(targetCallback, exceptionsHandler) {
        return async (req, res, next) => {
            try {
                await targetCallback(req, res, next);
            }
            catch (e) {
                const host = new execution_context_host_1.ExecutionContextHost([req, res, next]);
                exceptionsHandler.next(e, host);
            }
        };
    }
    /// .....
}

在这里插入图片描述

  1. 每次有异常被捕获时,都会判断是否有自定义过滤器,如果没有,则使用默认的异常过滤器
    class ExceptionsHandler extends base_exception_filter_1.BaseExceptionFilter {
    constructor() {
    // …
    }
    next(exception, ctx) {
    // 判断是否存在自定义过滤器
    if (this.invokeCustomFilters(exception, ctx)) {
    return;
    }
    // 执行全局异常过滤器的catch函数
    super.catch(exception, ctx);
    }
    invokeCustomFilters(exception, ctx) {
    // …
    }
    }
    在这里插入图片描述

  2. 默认的异常过滤器会校验抛出的异常是否是HttpException类。如果不是,则按未知异常处理。否则返回自定义异常

class BaseExceptionFilter {
    constructor(applicationRef) {
        this.applicationRef = applicationRef;
    }
    catch(exception, host) {
        const applicationRef = this.applicationRef ||
            (this.httpAdapterHost && this.httpAdapterHost.httpAdapter);
        // 判断exception是否是HttpEception的实例
        if (!(exception instanceof common_1.HttpException)) {
            // 处理未知异常
            return this.handleUnknownError(exception, host, applicationRef);
        }
        // 处理自定义异常
        const res = exception.getResponse();
        const message = (0, shared_utils_1.isObject)(res)
            ? res
            : {
                statusCode: exception.getStatus(),
                message: res,
            };
        const response = host.getArgByIndex(1);
        if (!applicationRef.isHeadersSent(response)) {
            applicationRef.reply(response, message, exception.getStatus());
        }
        else {
            applicationRef.end(response);
        }
    }
    handleUnknownError(exception, host, applicationRef) {
        // .....
    }
}

经过反复debug,发现在执行!(exception instanceof common_1.HttpException)时永远都是false,导致每次都被当做未知异常处理,查看文件
可是守卫抛出的UnauthorizedException类是继承HttpException的。
在Nestjs的仓库中,找到了一例和我问题相似的issue
● https://github.com/nestjs/nest/issues/8617
原来是因为包版本嵌套依赖导致的,A服务依赖了@nestjs/common包,而A服务依赖的守卫组件又有自己的@nestjs/common包,虽然两个类名相同,但由于隶属于不同的文件,所以他们必然是没有关系的。
比如下面的例子:

为什么会有嵌套依赖?

因为下载npm包时,会同时将dependencies的依赖下载到当前的node_modules,这样就会导致每个npm包可能都有自己的依赖和版本。这样会造成两个问题

  • 包版本可能不兼容
  • 两个包实例不同,导致instansof永远为false

如何解决嵌套依赖导致的问题?
像vuex,webpack,@nestjs/core等包的package.json中都有peerDependencies和devDependencies。

  • vuex
    在这里插入图片描述

  • webpack
    在这里插入图片描述

  • @nestjs/core
    在这里插入图片描述

根据packge.json中的约定,定义在peerDependencies内的包在被安装时不会被下载,它直接指向了根目录下的依赖(如果根目录没有安装,则启动时会因为找不到依赖而提醒安装),这样就避免了因包嵌套导致的依赖版本,实例相等性问题
定义在devDependencies内的包在被安装时不会下载,仅用于开发阶段使用,这样就不用在安装阶段去下载这些依赖

解决

现在问题已经找到,于是调整了@albedo-inc/albedo-auth-nestjs服务的package.json
将@nestjs/common和@nestjs/core从dependencies中删除
然后复制到到devDependencies和peerDependencies,限制守卫组件和 Nestjs的项目使用同一个@nestjs/common和@nestjs/core模块包。

在这里插入图片描述
在这里插入图片描述

通过使用 Postman工具测试鉴权失败的接口,可见已正常抛出自定义的UnauthorizedException错误。

在这里插入图片描述

相关文章:

  • JVM内存溢出问题排查
  • java计算机毕业设计门诊药品管理系统源码+数据库+系统+lw文档+mybatis+运行部署
  • 真知灼见|客户视图与工作台:金融行业呼叫中心领域驱动设计
  • spring-task进行任务调度
  • npm实现格式化时间---就是实现时间按照要求输出--moment包
  • webdriver API进阶
  • 除自身以外数组的乘积、找到所有数组中消失的数字、两数之和
  • 四川农信分布式核心设计及验证项目成果专家评审会召开
  • 快速知识蒸馏的视觉框架-来自卡耐基梅隆大学等单位
  • c++ 11 线程支持 (std::promise)
  • 一篇文章带你看清C语言中的类型转换规则
  • 单海军:行业AI平台赋能金融企业数智化转型
  • Jmeter接口自动化(十)断言
  • C++ 小游戏 视频及资料集(7)
  • 计算机网络笔记(王道考研) 第二章:物理层
  • 《剑指offer》分解让复杂问题更简单
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 2018一半小结一波
  • eclipse(luna)创建web工程
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 聚类分析——Kmeans
  • 模型微调
  • 前端之Sass/Scss实战笔记
  • 强力优化Rancher k8s中国区的使用体验
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 小程序 setData 学问多
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • #mysql 8.0 踩坑日记
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (10)STL算法之搜索(二) 二分查找
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (九)信息融合方式简介
  • (九十四)函数和二维数组
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (转)创业家杂志:UCWEB天使第一步
  • .bat文件调用java类的main方法
  • .Family_物联网
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .Net FrameWork总结
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • @property python知乎_Python3基础之:property
  • [2544]最短路 (两种算法)(HDU)
  • [BUG] Authentication Error
  • [C++] 统计程序耗时
  • [Codeforces1137D]Cooperative Game
  • [COI2007] Sabor
  • [CSS] 点击事件触发的动画