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.3.7 字段之序列化方式

    本文核心是带大家全面了解oinone的序列方式,包括支持的序列化类型、注意点、如果新增客户化序列化方式以及字段默认值的反序列化。 一、数据存储的序列化 (举例) 使用@Field注解的serialize属性来配置非字符串类型属性的序列化与反序列化方式,最终会以序列化后的字符串持久化到存储中。 ### Step1 新建PetItemDetail模型、并为PetItem添加两个字段 PetItemDetail继承TransientModel,增加两个字段,分别为备注和备注人 package pro.shushi.pamirs.demo.api.tmodel; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.TransientModel; import pro.shushi.pamirs.user.api.model.PamirsUser; @Model.model(PetItemDetail.MODEL_MODEL) @Model(displayName = "商品详情",summary = "商品详情",labelFields = {"remark"}) public class PetItemDetail extends TransientModel { public static final String MODEL_MODEL="demo.PetItemDetail"; @Field.String(min = "2",max = "20") @Field(displayName = "备注",required = true) private String remark; @Field(displayName = "备注人",required = true) private PamirsUser user; } 图3-3-7-1 PetItemDetail继承TransientModel 修改PetItem,增加两个字段petItemDetails类型为List和tags类型为List,并设置为不同的序列化方式,petItemDetails为JSON(缺省就是JSON,可不配),tags为COMMA。同时设置 @Field.Advanced(columnDefinition = varchar(1024)),防止序列化后存储过长 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.tmodel.PetItemDetail; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.enmu.NullableBoolEnum; import java.math.BigDecimal; import java.util.List; @Model.model(PetItem.MODEL_MODEL) @Model(displayName = "宠物商品",summary="宠物商品",labelFields = {"itemName"}) public class PetItem extends AbstractDemoCodeModel{ public static final String MODEL_MODEL="demo.PetItem"; @Field(displayName = "商品名称",required = true) private String itemName; @Field(displayName = "商品价格",required = true) private BigDecimal price; @Field(displayName = "店铺",required = true) @Field.Relation(relationFields = {"shopId"},referenceFields = {"id"}) private PetShop shop; @Field(displayName = "店铺Id",invisible = true) private Long shopId; @Field(displayName = "品种") @Field.many2one @Field.Relation(relationFields = {"typeId"},referenceFields = {"id"}) private PetType type; @Field(displayName = "品种类型",invisible = true) private Long typeId; @Field(displayName = "详情", serialize = Field.serialize.JSON, store = NullableBoolEnum.TRUE) @Field.Advanced(columnDefinition = "varchar(1024)") private List<PetItemDetail> petItemDetails; @Field(displayName = "商品标签",serialize = Field.serialize.COMMA,store = NullableBoolEnum.TRUE,multi = true) @Field.Advanced(columnDefinition = "varchar(1024)") private…

    2024年5月23日
    1.3K00
  • 4.1.22 框架之分布式缓存

    分布式缓存Oinone平台主要用到了Redis,为了让业务研发时可以无感使用RedisTemplate和StringRedisTemplate,已经提前注册好了redisTemplate和stringRedisTemplate,而且内部会自动处理相关特殊逻辑以应对多租户环境,小伙伴不能自己重新定义Redis的相关bean。 使用说明 配置说明 spring: redis: database: 0 host: 127.0.0.1 port: 6379 timeout: 2000 # cluster: # nodes: # – 127.0.0.1:6379 # timeout: 2000 # max-redirects: 7 jedis: pool: # 连接池中的最大空闲连接 默认8 max-idle: 8 # 连接池中的最小空闲连接 默认0 min-idle: 0 # 连接池最大连接数 默认8 ,负数表示没有限制 max-active: 8 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1 max-wait: -1 图4-1-22-1 分布式缓存配置说明 代码示例 package pro.shushi.pamirs.demo.core.service; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; @Component public class Test { @Autowired private RedisTemplate redisTemplate; @Autowired private StringRedisTemplate stringRedisTemplate } 图4-1-22-2 代码示例

    Oinone 7天入门到精通 2024年5月23日
    1.4K00
  • 5.3 基础支撑之用户与客户域

    一、三户概念 三户由来 介绍下经典的三户模型,它是电信运营支持系统的基础。三户模型即客户、用户和帐户,来源于etom的模型。这三者之间的关系应该是一个相互关联但又是独立的三个实体,这种关联只是一个归属和映射的关系,而三个实体本身是相互独立的,分别是体现完全不同的几个域的信息,客户是体现了社会域的信息,用户体现了业务域的信息,帐户体现的是资金域的信息。 客户:它是个社会化的概念,一个自然人或一个法人 用户:它是客户使用运营商开发的一个产品以及基于该产品之上的增值业务时,产生的一个实体。如果说一个客户使用了多个产品,那么一个客户就会对应好几个用户(即产品) 账户:它的概念起源于金融业,只是一个客户在运营商存放资金的实体,目的是为选择的产品付费 Oinone的三户 在原三户模型中【用户】是购买关系产生的产品与客户关系的服务实例,在互联网发展中用户的概念发生了非常大的变化,【用户】概念变成了:使用者,是指使用电脑或网络服务的人,通常拥有一个用户账号,并以用户名识别。而且新概念在互联网强调用户数的大背景下已经被普遍介绍,再去强调电信行业的用户概念就会吃力不讨好。而且不管是企业应用领域和互联网领域,原用户概念都显得过于复杂和没有必要。也就有了特色的oinone的三户模型: 客户:它是个社会化的概念,一个自然人或一个法人 用户:使用者,是指使用电脑或网络服务的人,通常拥有一个用户账号,并以用户名识别 账户:它的概念起源于金融业,只是一个客户在运营商存放资金的实体,目的是为选择的产品付费 二、Oinone的客户与用户 三户模型是构建上层应用的基础支撑能力,任何业务行为都跟这里两个实体脱不了干系。以客户为中心建立商业关系与商业行为主体,以用户为中心构建一致体验与操作行为主体。在底层设计上二者相互独立并无关联,由上层应用自行作关联绑定,往往在登陆时在Session的处理逻辑中会根据【用户】去找到对应一个或多个【商业(主体)客户】,Session的实现可以参考4.1.20【框架之Session】一文。 图5-3-1 Oinone的客户与用户 客户设计说明 PamirsPartner作为商业关系与商业行为的主体,派生了两个子类PamirsCompany与PamirsPerson分别对应:公司(法人)客户、自然人客户 公司(法人)客户PamirsCompany对应多个组织部门PamirsDepartment,公司(法人)客户PamirsCompany对应多个员工PamirsEmployee 部门PamirsDepartment对应一个公司(法人)客户PamirsCompany,对应多个员工PamirsEmployee 员工PamirsEmployee对应多个部门PamirsDepartment,对应一个或多个公司(法人)客户PamirsCompany,其中有一个主的 用户设计说明 PamirsUser作为一致体验与操作行为主体,本身绑定登陆账号,并且可以关联多个三方登陆账户PamirsUserThirdParty 客户与用户如何关联(举例) 例子设计: 新建demo系统的PetComany和PetEmployee,用PetEmployee去关联用户。 当用户登陆时,根据用户Id找到PetEmployee,在根据PetEmployee找到PetComany,把PetComany放到Session中去 修改PetShop模型关联一个PamirsPartner,PamirsPartner的信息从Session取。 Step1 pamirs-demo-api工程增加依赖,并且DemoModule增加对BusinessModule的依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-business-api</artifactId> </dependency> 图5-3-2 pamirs-demo-api工程增加依赖 在DemoModule类中通过@Module.dependencies中增加BusinessModule.MODULE_MODULE @Module( dependencies = { BusinessModule.MODULE_MODULE} ) 图5-3-3 声明对BusinessModule的依赖 Step2 新建PetComany和PetEmployee,以及对应的服务 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.business.api.model.PamirsEmployee; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.user.api.model.PamirsUser; @Model.model(PetEmployee.MODEL_MODEL) @Model(displayName = "宠物公司员工",labelFields = "name") public class PetEmployee extends PamirsEmployee { public static final String MODEL_MODEL="demo.PetEmployee"; @Field(displayName = "用户") private PamirsUser user; } 图5-3-4 新建PetEmployee package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.business.api.entity.PamirsCompany; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.model(PetCompany.MODEL_MODEL) @Model(displayName = "宠物公司",labelFields = "name") public class PetCompany extends PamirsCompany { public static final String MODEL_MODEL="demo.PetCompany"; @Field.Text @Field(displayName = "简介") private String introductoin; } 图5-3-5 新建PetComany package pro.shushi.pamirs.demo.api.service; import pro.shushi.pamirs.demo.api.model.PetEmployee; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; @Fun(PetEmployeeQueryService.FUN_NAMESPACE) public interface PetEmployeeQueryService { String FUN_NAMESPACE ="demo.PetEmployeeQueryService"; @Function PetEmployee queryByUserId(Long userId); } 图5-3-6 新建PetEmployee对应服务 package pro.shushi.pamirs.demo.core.service; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.model.PetEmployee; import pro.shushi.pamirs.demo.api.service.PetEmployeeQueryService; import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; @Fun(PetEmployeeQueryService.FUN_NAMESPACE) @Component public class PetEmployeeQueryServiceImpl implements PetEmployeeQueryService { @Override @Function public PetEmployee queryByUserId(Long userId) { if(userId==null){ return null; } QueryWrapper<PetEmployee> queryWrapper = new QueryWrapper<PetEmployee>().from(PetEmployee.MODEL_MODEL).eq("user_id", userId); return…

    2024年5月23日
    1.0K00
  • 3.5.6.4 动作的配置

    在3.5.3【Action的类型】一文中,我们介绍Action的几种类型,以及组合动作。 通用配置 配置项 可选值 默认值 作用 name 动作名称 label 显示名称 icon 图标 type primary defaultlink primary 按钮类型样式,支持主要样式、次要样式以及链接样式。 bizStyle defaultsuccesswarningdangerinfo default 按钮业务样式,支持成功(green)、警告(yellow)、危险(red)、信息(grey)四种样式。 invisible truefalse condition false 展示规则,有简单的true/false显隐,也支持复杂的表达式 disabled truefalse condition 根据动作上下文类型进行自动推断 是否禁用自动推断规则:当上下文类型为【单行】时,相当于使用表达式LIST_COUNT(context.activeRecords) != 1当上下文类型为【多行】时,相当于使用表达式LIST_COUNT(context.activeRecords) <= 1当上下文类型为【单行或多行】时,相当于使用表达式LIST_COUNT(context.activeRecords) == 0 disabledTitle string 根据动作上下文类型进行自动推断 禁用悬浮提示 表3-5-6-12 动作通用配置 二次确认配置 二次确认框默认支持两种模式,对话框和气泡框; 对话框 图3-5-6-51 对话框提示 气泡框 图3-5-6-52 气泡框警告 配置项 配置项 可选值 默认值 作用 备注 confirm string 二次确认提示文字 配置后开启二次确认 confirmType POPPER(气泡提示框) MODAL(对话框) POPPER 确认框类型 confirmPosition TM(按钮上方) BM(按钮下方) LM(按钮左侧) RM(按钮右侧) BM 确认框位置 气泡框该配置生效 enterText 确定 确定按钮文字 cancelText 取消 取消按钮文字 表3-5-6-13 配置项 弹出层动作配置(窗口动作ViewAction) 目前平台对于弹出层支持了两种展示形式。弹窗(modal/dialog)和抽屉(drawer) 支持两种配置方式【内嵌视图配置】和【引用已有页面】,内嵌视图配置优先于引用已有页面。 内嵌视图配置 该配置对于弹窗和抽屉均适用。 <action name="窗口动作名称" label="创建"> <view model="模型编码" type="form"> <template slot="form" widget="form"> <field data="id" invisible="true" /> <field data="code" label="编码" widget="Input" /> <field data="name" label="名称" widget="Input" /> </template> <template slot="footer"> <action name="$$internal_DialogCancel" label="关闭" type="default" /> <action name="create" label="确定" /> </template> </view> </action> 图3-5-6-53 内嵌视图配置 引用已有页面配置 该配置对于弹窗和抽屉均适用。 <view model="模型编码" type="form"> <template slot="form" widget="form"> <field data="id" invisible="true" /> <field data="code" label="编码" widget="Input" /> <field data="name" label="名称" widget="Input" /> </template> <template slot="footer"> <action name="$$internal_DialogCancel" label="关闭" type="default" /> <action name="create" label="确定" /> </template> </view> 图3-5-6-54 引用已有页面示例 <action name="窗口动作名称" label="创建" resViewName="$viewName$" /> 图3-5-6-55 引用已有页面 弹窗 当窗口动作的路由方式(target)为dialog时,内嵌视图/引用页面将以弹窗形式展示在页面上。 配置项 配置项 可选值 默认值 作用 title…

    2024年5月23日
    1.1K00
  • 4.1.20 框架之Session

    在日常开发中,我们经常需要把一些通用的信息放入程序执行的上下文中,以便业务开发人员快速获取。那么oinone的PamirsSession就是来解决此类问题的。 一、PamirsSession介绍 在oinone的体系中PamirsSession是执行上下文的承载,您能从中获取业务基础信息、指令信息、元数据信息、环境信息、请求参数,以及前后端MessageHub等。在前面的学习过程中我们已经多次接触到了如何使用PamirsSession: 在4.1.19【框架之网关协议-后端占位符】一文中,使用PamirsSession.getUserId()来获取当前登入用户Id,诸如此类的业务基础信息; 在4.1.18【框架之网关协议-variables变量】一文中,使用PamirsSession.getRequestVariables()得到PamirsRequestVariables对象,进而获取前端请求的相关信息; 在4.1.5【模型之持久层配置】一文中,使用PamirsSession.directive(),来操作元位指令系统,进而影响执行策略; 在4.1.13【Action之校验】、3.4.1【构建第一个Function】等文章中,都用到PamirsSession.getMessageHub()来设置返回消息。 二、构建模块自身Session(举例) 不同的应用场景对PamirsSession的诉求是不一样的,这个时候我们就可以去扩展PamirsSession来达到我们的目的 构建模块自身Session的步骤 构建自身特有的数据结构XSessionData 对XSessionData进行线程级缓存封装 利用Hook机制初始化XSessionData并放到ThreadLocal中 定义自身XSessionApi 实现XSessionApi接口、SessionClearApi。在请求结束时会调用SessionClearApi的clear方法 定义XSession继承PamirsSession 扩展PamirsSession的经典案例设计图 图4-1-20-1 扩展PamirsSession的经典案例设计图 构建Demo应用自身Session 下面的例子为给Session放入当前登陆用户 Step1 新建DemoSessionData类 构建自身特有的数据结构DemoSessionData,增加一个模型为PamirsUser的字段user,DemoSessionData用Data注解,注意要用Oinone平台提供的@Data package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.annotation.fun.Data; import pro.shushi.pamirs.user.api.model.PamirsUser; @Data public class DemoSessionData { private PamirsUser user; } 图4-1-20-2 新建DemoSessionData类 Step2 新建DemoSessionCache 对DemoSessionData进行线程级缓存封装 package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApiFactory; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.service.UserService; public class DemoSessionCache { private static final ThreadLocal<DemoSessionData> BIZ_DATA_THREAD_LOCAL = new ThreadLocal<>(); public static PamirsUser getUser(){ return BIZ_DATA_THREAD_LOCAL.get()==null?null:BIZ_DATA_THREAD_LOCAL.get().getUser(); } public static void init(){ if(getUser()!=null){ return ; } Long uid = PamirsSession.getUserId(); if(uid == null){ return; } PamirsUser user = CommonApiFactory.getApi(UserService.class).queryById(uid); if(user!=null){ DemoSessionData demoSessionData = new DemoSessionData(); demoSessionData.setUser(user); BIZ_DATA_THREAD_LOCAL.set(demoSessionData); } } public static void clear(){ BIZ_DATA_THREAD_LOCAL.remove(); } } 图4-1-20-3 对DemoSessionData进行线程级缓存封装 Step3 新建DemoSessionHook 利用Hook机制,调用DemoSessionCache的init方法初始化DemoSessionData并放到ThreadLocal中。 @Hook(module= DemoModule.MODULE_MODULE), 规定只有增对DemoModule模块访问的请求该拦截器才会生效,不然其他模块的请求都会被DemoSessionHook拦截。 package pro.shushi.pamirs.demo.core.hook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.session.DemoSessionCache; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.core.faas.HookBefore; import pro.shushi.pamirs.meta.api.dto.fun.Function; @Component public class DemoSessionHook implements HookBefore { @Override @Hook(priority = 1,module = DemoModule.MODULE_MODULE) public Object run(Function function, Object… args) { DemoSessionCache.init(); return function; } } 图4-1-20-4 新建DemoSessionHook Step4 新建DemoSessionApi package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApi; import pro.shushi.pamirs.user.api.model.PamirsUser; public interface DemoSessionApi extends CommonApi { PamirsUser getUser(); } 图4-1-20-5 新建DemoSessionApi Step5…

    2024年5月23日
    1.3K00

Leave a Reply

登录后才能评论