4.2.6 框架之网络请求-拦截器

在整个http的链路中,异常错误对前端来说尤为重要,他作用在很多不同的场景,通用的比如500, 502等; 一个好的软件通常需要在不同的错误场景中做不同的事情。当用户cookie失效时,希望能自动跳转到登录页;当用户权限发生变更时,希望能跳转到一个友好的提示页;那么如何满足这些个性化的诉求呢?接下来让我们一起了解oinone前端网络请求-拦截器。

一、入口

在src目录下main.ts中可以看到VueOioProvider,这是系统功能提供者的注册入口

image.png

图4-2-6-1 VueOioProvider

import interceptor from './middleware/network-interceptor';
VueOioProvider(
  {
    http: {
      callback: interceptor
    }
  },
  []
);

图4-2-6-2 拦截器的申明入口

二、middleware

在项目初始化时使用CLI构建初始化前端工程,在src/middleware有拦截器的默认实现:

image.png

图4-2-6-3 在src/middleware有拦截器的默认实现

三、interceptor

interceptor在请求返回后触发,interceptor有两个回调函数,error和next

error参数

  • graphQLErrors 处理业务异常

  • networkError 处理网络异常

next

  • extensions 后端返回扩展参数
const interceptor: RequestHandler = (operation, forward) => {
  return forward(operation).subscribe({
    error: ({ graphQLErrors, networkError }) => {
            console.log(graphQLErrors, networkError);
      // 默认实现 => interceptor error 
    },
    next: ({ extensions }) => {
            console.log(extensions);
      // 默认实现 => interceptor next 
    },
  });
};

图4-2-6-4 后端返回扩展参数

四、interceptor error

// 定义错误提示等级
const DEFAULT_MESSAGE_LEVEL = ILevel.ERROR;
// 错误提示等级 对应提示的报错
const MESSAGE_LEVEL_MAP = {
  [ILevel.ERROR]: [ILevel.ERROR],
  [ILevel.WARN]: [ILevel.ERROR, ILevel.WARN],
  [ILevel.INFO]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO],
  [ILevel.SUCCESS]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS],
  [ILevel.DEBUG]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS, ILevel.DEBUG]
};
// 错误提示通用函数
const notificationMsg = (type: string = 'error', tip: string = '错误', desc: string = '') => {
  notification[type]({
    message: tip,
    description: desc
  });
};
// 根据错误等级 返回错误提示和类型
const getMsgInfoByLevel = (level: ILevel) => {
  let notificationType = 'info';
  let notificationText = translate('kunlun.common.info');
  switch (level) {
    case ILevel.DEBUG:
      notificationType = 'info';
      notificationText = translate('kunlun.common.debug');
      break;
    case ILevel.INFO:
      notificationType = 'info';
      notificationText = translate('kunlun.common.info');
      break;
    case ILevel.SUCCESS:
      notificationType = 'success';
      notificationText = translate('kunlun.common.success');
      break;
    case ILevel.WARN:
      notificationType = 'warning';
      notificationText = translate('kunlun.common.warning');
      break;
  }
  return {
    notificationType,
    notificationText
  };
};

error: ({ graphQLErrors, networkError }) => {
      if (graphQLErrors) {
        graphQLErrors.forEach(async ({ message, locations, path, extensions }) => {
          let { errorCode, errorMessage, messages } = extensions || {};
          // FIXME: extensions.errorCode
          if (errorCode == null) {
            const codeArr = /code: (\d+),/.exec(message);
            if (codeArr) {
              errorCode = Number(codeArr[1]);
            }
          }
          if (errorMessage == null) {
            const messageArr = /msg: (.*),/.exec(message);
            if (messageArr) {
              errorMessage = messageArr[1];
            }
          }
          // 错误通用提示
          if (messages && messages.length) {
            messages.forEach((m) => {
              notificationMsg('error', translate('kunlun.common.error'), m.message || '');
            });
          } else {
            notificationMsg('error', translate('kunlun.common.error'), errorMessage || message);
          }
            // 提示扩展信息 根据错误等级来提示对应级别的报错
          const extMessage = getValue(response, 'extensions.messages');
          if (extMessage && extMessage.length) {
            const messageLevelArr = MESSAGE_LEVEL_MAP[DEFAULT_MESSAGE_LEVEL];
            extMessage.forEach((m) => {
              if (messageLevelArr.includes(m.level)) {
                const { notificationType, notificationText } = getMsgInfoByLevel(m.level);
                notificationMsg(notificationType, notificationText, m.message || '');
              }
            });
          }

          // 消息模块的用户未登录错误码
          const MAIL_USER_NOT_LOGIN = 20080002;
          // 基础模块的用户未登录错误码
          const BASE_USER_NOT_LOGIN_ERROR = 11500001;
          if (
            [MAIL_USER_NOT_LOGIN, BASE_USER_NOT_LOGIN_ERROR].includes(Number(errorCode)) &&
            location.pathname !== '/auth/login'
          ) {
            const redirect_url = location.pathname;
            location.href = `/login?redirect_url=${redirect_url}`;
          }
          /**
           * 应用配置异常跳转至通用的教程页面
           */
          // 模块参数配置未完成
          const BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR = 11500004;
          if ([BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR].includes(Number(errorCode))) {
            const action = getValue(response, 'extensions.extra.action');
            if (action) {
              Action.registerAction(action.model, action);
              const searchParams: string[] = [];
              searchParams.push(`module=${action.module}`);
              searchParams.push(`model=${action.model}`);
              searchParams.push(`viewType=${action.viewType}`);
              searchParams.push(`actionId=${action.id}`);
              const href = `${origin}/page;${searchParams.join(';')}`;
              location.href = href;
            }
          }
        });
      }
      if (networkError) {
        const { name, result } = networkError;
        const errMsg = (result && result.message) || `${networkError}`;
        if (name && result && result.message) {
          notification.error({
            message: translate('kunlun.common.error'),
            description: `[${name}]: ${errMsg}`,
          });
        }
      }
    }

图4-2-6-5 interceptor error

四、interceptor next

next: ({ extensions }) => {
  if (extensions) {
    const messages = extensions.messages as {
      level: 'SUCCESS';
      message: string;
    }[];
    if (messages)
      messages.forEach((msg) => {
        notification.success({
          message: '操作成功',
          description: msg.message,
        });
      });
  }
}

图4-2-6-6 interceptor next

六、完整代码

import { NextLink, Operation } from 'apollo-link';
import { notification } from 'ant-design-vue';
import getValue from 'lodash/get';
import { Action, ILevel, translate } from '@kunlun/dependencies';

interface RequestHandler {
  (operation: Operation, forward: NextLink): Promise<any> | any;
}

const DEFAULT_MESSAGE_LEVEL = ILevel.ERROR;
const MESSAGE_LEVEL_MAP = {
  [ILevel.ERROR]: [ILevel.ERROR],
  [ILevel.WARN]: [ILevel.ERROR, ILevel.WARN],
  [ILevel.INFO]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO],
  [ILevel.SUCCESS]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS],
  [ILevel.DEBUG]: [ILevel.ERROR, ILevel.WARN, ILevel.INFO, ILevel.SUCCESS, ILevel.DEBUG]
};

const notificationMsg = (type: string = 'error', tip: string = '错误', desc: string = '') => {
  notification[type]({
    message: tip,
    description: desc
  });
};

const getMsgInfoByLevel = (level: ILevel) => {
  let notificationType = 'info';
  let notificationText = translate('kunlun.common.info');
  switch (level) {
    case ILevel.DEBUG:
      notificationType = 'info';
      notificationText = translate('kunlun.common.debug');
      break;
    case ILevel.INFO:
      notificationType = 'info';
      notificationText = translate('kunlun.common.info');
      break;
    case ILevel.SUCCESS:
      notificationType = 'success';
      notificationText = translate('kunlun.common.success');
      break;
    case ILevel.WARN:
      notificationType = 'warning';
      notificationText = translate('kunlun.common.warning');
      break;
  }
  return {
    notificationType,
    notificationText
  };
};

const interceptor: RequestHandler = (operation, forward) => {
  return forward(operation).subscribe({
    error: ({ graphQLErrors, networkError, response }) => {
      if (graphQLErrors) {
        graphQLErrors.forEach(async ({ message, locations, path, extensions }) => {
          let { errorCode, errorMessage, messages } = extensions || {};
          // FIXME: extensions.errorCode
          if (errorCode == null) {
            const codeArr = /code: (\d+),/.exec(message);
            if (codeArr) {
              errorCode = Number(codeArr[1]);
            }
          }
          if (errorMessage == null) {
            const messageArr = /msg: (.*),/.exec(message);
            if (messageArr) {
              errorMessage = messageArr[1];
            }
          }
          if (messages && messages.length) {
            messages.forEach((m) => {
              notificationMsg('error', translate('kunlun.common.error') || '', m.message || '');
            });
          } else {
            notificationMsg('error', translate('kunlun.common.error') || '', errorMessage || message);
          }

          const extMessage = getValue(response, 'extensions.messages');
          if (extMessage && extMessage.length) {
            const messageLevelArr = MESSAGE_LEVEL_MAP[DEFAULT_MESSAGE_LEVEL];
            extMessage.forEach((m) => {
              if (messageLevelArr.includes(m.level)) {
                const { notificationType, notificationText } = getMsgInfoByLevel(m.level);
                notificationMsg(notificationType, notificationText, m.message || '');
              }
            });
          }
          console.log(extMessage);

          // 消息模块的用户未登录错误码
          const MAIL_USER_NOT_LOGIN = 20080002;
          // 基础模块的用户未登录错误码
          const BASE_USER_NOT_LOGIN_ERROR = 11500001;
          if (
            [MAIL_USER_NOT_LOGIN, BASE_USER_NOT_LOGIN_ERROR].includes(Number(errorCode)) &&
            location.pathname !== '/auth/login'
          ) {
            const redirect_url = location.pathname;
            location.href = `/login?redirect_url=${redirect_url}`;
          }
          /**
           * 应用配置异常跳转至通用的教程页面
           */
          // 模块参数配置未完成
          const BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR = 11500004;
          if ([BASE_SYSTEM_CONFIG_IS_NOT_COMPLETED_ERROR].includes(Number(errorCode))) {
            const action = getValue(response, 'extensions.extra.action');
            if (action) {
              Action.registerAction(action.model, action);
              const searchParams: string[] = [];
              searchParams.push(`module=${action.module}`);
              searchParams.push(`model=${action.model}`);
              searchParams.push(`viewType=${action.viewType}`);
              searchParams.push(`actionId=${action.id}`);
              const href = `${origin}/page;${searchParams.join(';')}`;
              location.href = href;
            }
          }
        });
      }
      if (networkError) {
        const { name, result } = networkError;
        const errMsg = (result && result.message) || `${networkError}`;
        if (name && result && result.message) {
          notification.error({
            message: translate('kunlun.common.error') || '',
            description: `[${name}]: ${errMsg}`
          });
        }
      }
    }
  });
};

export default interceptor;

图4-2-6-7 完整代码

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9307.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日 am8:31
下一篇 2024年5月23日 am8:33

相关推荐

  • 3.5.6 DSL配置大全(略)

    因为默认视图很难满足客户的个性化需求,所以日常开发中view的配置是避免不了的。本系列篇是比较全面地介绍View配置的各个方面涉及:视图、字段、动作、布局等

    Oinone 7天入门到精通 2024年5月23日
    1.0K00
  • 页面设计

    1. 功能说明 页面设计时界面设计器中「页面」模块的设计入口,在这个界面,进行页面的设置、搭建、设计、排版。 主要分为顶部操作栏、左侧工具栏、中部设计画布区、右侧属性面板。 2. 操作栏 进入页面设计,顶部显示了页面的标题,以及返回、发布等操作。 2.1 发布 页面设计完成后,点击「发布」运行页面生效。若不点击发布,页面也有自动保存的功能,但在发布前,自动保存也等同于草稿,不会正式生效。 发布时如果有属性不符合校验规则(必填的属性未填、输入的内容校验不通过),会发布失败,相应的字段会特殊标记,需要查看并修改属性。 2.2 显示/隐藏母版 进入页面设计时默认不展示母版,可以手动操作显示母版 3. 工具栏 左侧的工具栏中包括组件库、页面设置等模块。 3.1 组件库 组件库中包含组件和模型,组件是当前设计器支持的所有组件,模型是页面所在模型下的所有字段和动作。 3.1.1 组件 组件中展示了系统支持的所有组件。包含 1)布局类组件,如分组、选项卡等,使用布局组件可以将页面进字段分类、分页; 2)字段类组件,如单行文本、整数、日期等等,使用字段类组件时都会在模型下对应创建一个字段; 3)动作类组件,如跳转动作、提交动作、链接动作等。 3.1.2 模型 组件库顶部,由组件可切换为模型:模型选项下,会展示当前模型的所有字段,以及系统默认动作。可以直接拖拽字段至设计画布中,会应用形成某个组件,可对组件进行多样的属性设置,优化交互。 3.1.2 组件和模型有什么区别 1展示内容维度不同 组件中展示的内容是组件信息,如分组、选项卡、单行文本、文件上传等;模型中展示的是模型下已有的所有字段。 2使用功能不同 组件中的组件使用前需要在模型中创建一个字段,当然,创建好的字段也会存在于模型中;模型中的字段可直接使用,并且使用时会在设计画布中对应生成个默认组件。 3使用场景不同 如果模型中已经存在目标字段,应直接选择从模型中拖拽字段;如果模型中没有需要的字段,可以在页面中增加一个组件,实际上也是在新增一个字段。 3.2 页面设置 页面设置中可以修改当前页面的标题、分组、页面描述,同时也是给页面上传缩略图的唯一入口。 4. 设计画布 将组件或字段拖拽至设计画布区,会生成样式。点击组件,右侧可对其进行设置,大部分属性可实时在画布中展示效果。 5. 属性面板 右侧属性面板抽屉中可以设置属性或查看字段信息,通过不同的属性配置化实现组件的多样化。 5.1 属性 属性中包括基本信息(如标题、占位提示、描述说明等)、校验信息(如是否必填、长度校验等)、交互信息(如排序方式、是否展示计数器等) 5.2 字段 首次从组件中拖拽,会先展示字段信息,并且是需要先创建一个字段,字段创建成功后,再次切换字段选项,只读展示当前组件所对应的字段基本信息。同理,如果是直接从字段中拖拽,字段选项中,只读展示当前字段基本信息。

    2024年6月20日
    3.3K00
  • 数据类

    1.数据类 1.1 新增数据 新增数据节点可以为任意模型通过表达式新增数据,包含两种模式,「节点执行完即增加业务数据」功能下新增的数据也可以同步触发流程,相反「节点执行完不增加业务数据」则不会触发新增业务数据关联的流程,数据仅可用于后续流程使用。 流程增加的数据在节点执行时就生效。 1.2 更新数据 更新数据节点可以为任意模型通过表达式更新数据。 1.3 获取数据 当流程需要调用触发模型之外的模型中的数据时就需要使用获取数据的功能。 需要选择获取单条/多条数据,选择一个获取数据的模型,可以设置一些筛选项减少不必要的数据。最后需要设置未获取到数据时的执行方式。 继续执行:跳过本次数据获取,继续执行流程。 向模型中新增数据后继续执行:新增数据来供后续节点使用,新增数据同时存入数据库中。 终止流程:终止流程将结束该流程,不管之后是否还有流程节点。 1.4 删除数据 删除数据节点可以将流程节点上面的模型数据从数据库中删除。 1.5 更新流程参数 可以将「流程配置」中的「流程参数」进行修改,仅有变更过的流程参数才能其他节点选中。 1.6 引用逻辑 可以在流程中使用模块自带的逻辑或使用低代码设计的逻辑。

    2024年5月23日
    1.2K00
  • 3.4.2 函数的开放级别与类型

    一、函数开放级别 我们在日常开发中通常会因为安全性,为方法定义不同的开放层级,或者通过应用分层把需要对web开放的接口统一定义在一个独立的应用中。oinone也提供类似的策略,所有逻辑都通过Function来归口统一管理,所以在Function是可以定义其开放级别有API、REMOTE、LOCAL三种类型,配置可多选。 四种自定义新增方式与开放级别的对应关系 函数 本地调用(LOCAL) 远程调用(REMOTE) 开放(API) 伴随模型新增函数 支持 支持【默认】 支持 独立新增函数绑定到模型 支持 支持【默认】 支持 独立新增函数只作公共逻辑单元 支持 支持【默认】 伴随ServerAction新增函数 必选 表3-4-2-1 四种自定义新增方式与开放级别的对应关系 远程调用(REMOTE) 如果函数的开放级别为本地调用,则不会发布远程服务和注册远程服务消费者 非数据管理器函数 提供者:如果函数定义在当前部署包的启动应用中,则主动发布远程服务提供者。 消费者:如果函数定义在部署依赖包中但未在当前部署包的启动应用中,则系统会默认注册远程消费者。发布注册的远程服务使用命名空间和函数编码进行路由。 所以非数据管理器函数的消费者并不需要感知该服务是否是本地提供还是远程提供。而服务提供者也不需要手动注册远程服务。 数据管理器类函数 提供者:如果数据管理器函数所在模型定义在当前部署包的启动应用中,则系统会主动发布数据管理器的远程服务作为数据管理器的远程服务提供者; 消费者:如果模型定义在部署依赖包中但未在当前部署包的启动应用中,则系统会主动注册数据管理器的远程服务消费者。 所以数据管理器类函数的消费者与服务提供者并不需要感知函数的远程调用。 二、函数类型 函数的类型语义分为:增、删、改、查,在编程模式下目前用于Function为API级别,生成GraphQL的Schema时放在query还是mutation。查放在query,其余放mutation。 三、函数分类 TBD 在无代码编辑器中,函数分类主要用函数选择的分类管理。

    Oinone 7天入门到精通 2024年5月23日
    1.4K00

Leave a Reply

登录后才能评论