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.9 字段类型之关系与引用

    有关系与引用类型才让oinone具备完整的描述模型与模型间关系的能力 在PetShop以及其代理模型中已经上用到了O2M、M2O字段,分别如petItems(PetItem)和create(PamrisUser)字段,但是没有过多的讲解。本文重点举例RELATED、M2M、O2M,至于M2O留给大家自行尝试。 一、引用类型(举例) 业务类型 Java类型 数据库类型 规则说明 RELATED 基本类型或关系类型 不存储或varchar、text 引用字段【数据库规则】:点表达式最后一级对应的字段类型;数据库字段值默认为Java字段的序列化值,默认使用JSON序列化【前端交互规则】:点表达式最后一级对应的字段控件类型 表3-3-9-1 字段引用类型 Step1 修改PetShopProxy类 为PetShopProxy类新增一个引用字段relatedShopName,并加上@Field.Related("shopName")注解 为PetShopProxy类新增一个引用字段createrId,并加上@Field.Related({"creater","id"})注解 package pro.shushi.pamirs.demo.api.proxy; import pro.shushi.pamirs.demo.api.model.PetShop; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.enmu.ModelTypeEnum; import pro.shushi.pamirs.user.api.model.PamirsUser; @Model.model(PetShopProxy.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.PROXY) @Model(displayName = "宠物店铺代理模型",summary="宠物店铺代理模型") public class PetShopProxy extends PetShop { public static final String MODEL_MODEL="demo.PetShopProxy"; @Field.many2one @Field(displayName = "创建者",required = true) @Field.Relation(relationFields = {"createUid"},referenceFields = {"id"}) private PamirsUser creater; @Field.Related("shopName") @Field(displayName = "引用字段shopName") private String relatedShopName; @Field.Related({"creater","id"}) @Field(displayName = "引用创建者Id") private String createrId; } 图3-3-9-1 修改PetShopProxy类 Step2 重启系统查看效果 我们发现商店管理-列表页面多出了两个有值字段:引用字段shopName和引用创建者Id 图3-3-9-2 商店管理-列表页面新增两个有值字段 二、关系类型 业务类型 Java类型 数据库类型 规则说明 O2O 模型/DataMap 不存储或varchar、text 一对一关系 M2O 模型/DataMap 不存储或varchar、text 多对一关系 O2M List<模型/DataMap> 不存储或varchar、text 一对多关系 M2M List<模型/DataMap> 不存储或varchar、text 多对多关系 表3-3-9-2 字段关系类型 多值字段或者关系字段需要存储,默认使用JSON格式序列化。多值字段数据库字段类型默认为varchar(1024);关系字段数据库字段类型默认为text。 关系字段 关联关系用于描述模型间的关联方式: 多对一关系,主要用于明确从属关系 一对多关系,主要用于明确从属关系 多对多关系,主要用于弱依赖关系的处理,提供中间模型进行关联关系的操作 一对一关系,主要用于多表继承和行内合并数据 图3-3-9-3 字段关联关系 名词解释 关联关系比较重要的名词解释如下: 关联关系:使用relation表示,模型间的关联方式的一种描述,包括关联关系类型、关联关系双边的模型和关联关系的读写 关联关系字段:业务类型ttype为O2O、O2M、M2O或M2M的字段 关联模型:使用references表示,自身模型关联的模型 关联字段:使用referenceFields表示,关联模型的字段,表示关联模型的哪些字段与自身模型的哪些字段建立关系 关系模型:自身模型 关系字段:使用relationFields表示,自身模型的字段,表示自身模型的哪些字段与关联模型的哪些字段建立关系 中间模型,使用through表示,只有多对多存在中间模型,模型的relationship=true 举例M2M关系类型 多对多关系,主要用于弱依赖关系的处理,提供中间模型进行关联关系的操作。这也是在业务开发中很常见用于描述单据间关系,该例子会举例两种方式描述多对多关系中间表,一是中间表没有在系统显示定义模型,二种是中间表显示定义模型。第一种往往仅是维护多对多关系,第二种往往用于多对多关系中间表自身也需要管理有业务含义,中间表模型还经常额外增加其他字段。 一是中间表没有在系统显示定义模型:如果出现跨模块的场景,在分布式环境下两个模块独立启动,有可能会导致系统关系表被删除的情况发生,因为没有显示定义中间表模型,中间表的模型所属模块会根据两边模型的名称计算,如果刚好被计算到非关系字段所属模型的模块。那么单独启动非关系字段所属模型的模块,则会导致删除关系表。 为什么不直接把中间表的模型所属模块设置为关系字段所属模型的模块?因为如果这样做,当模型两边都定义了多对多关系字段则会导致M2M关系表的所属模块出现混乱。 所以这里建议大家都选用:第二种中间表显示定义模型,不论扩展性还是适应性都会好很多。请用:through=XXXRelationModel.MODEL_MODEL 或者 throughClass=XXXRelationModel.class Step1 新建宠物达人模型,并分别为宠物商品和宠物商店增加 到宠物达人模型的字段 新建宠物达人模型PetTalent package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.model(PetTalent.MODEL_MODEL) @Model(displayName = "宠物达人",summary="宠物达人",labelFields ={"name"}) public class PetTalent extends AbstractDemoIdModel{ public static final String MODEL_MODEL="demo.PetTalent"; @Field(displayName = "达人") private String name; } 图3-3-9-4 新建宠物达人模型PetTalent 修改宠物商品模型,新增many2many字段petTalents,类型为List ,并加上注解@Field.many2many(relationFields = {"petItemId"},referenceFields = {"petTalentId"},through = PetItemRelPetTalent.MODEL_MODEL),through为指定关联中间表。 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.tmodel.PetItemDetail; import pro.shushi.pamirs.meta.annotation.Field; import…

    2024年5月23日
    1.6K00
  • 3.5.1 构建第一个Menu

    在前面章节中我们也涉及到菜单,因为菜单我们模块就是地图、导航,没有地图、导航就无法畅游模块并进行相关业务操作。在3.3.4【模块的继承】一文关于多表继承的内容就有涉及到菜单的初始化,本文将展开介绍初始化Menu的两种方式分别是:注解式、数据初始化式。 注解式(举例) Step1 分析现有菜单注解 用@UxMenus声明DemoMenus为菜单初始化入口,同时该类在DemoModule配置扫描路径中,那么通过DemoMenus初始化的菜单都挂在demo_core这个模块上。 如果采用这种模式,建议同一个模块的菜单都只配置在一处 package pro.shushi.pamirs.demo.core.init; import pro.shushi.pamirs.boot.base.constants.ViewActionConstants; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute; import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenu; import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenus; import pro.shushi.pamirs.demo.api.model.*; import pro.shushi.pamirs.demo.api.proxy.PetShopProxy; import pro.shushi.pamirs.demo.api.proxy.PetShopProxyA; import pro.shushi.pamirs.demo.api.proxy.PetShopProxyB; @UxMenus public class DemoMenus implements ViewActionConstants { @UxMenu("商店管理")@UxRoute(PetShopProxy.MODEL_MODEL) class PetShopProxyMenu{} @UxMenu("商店管理A")@UxRoute(PetShopProxyA.MODEL_MODEL) class PetShopProxyAMenu{} @UxMenu("商店管理B")@UxRoute(PetShopProxyB.MODEL_MODEL) class PetShopProxyBMenu{} @UxMenu("商品管理")@UxRoute(PetItem.MODEL_MODEL) class ItemMenu{} @UxMenu("宠狗商品")@UxRoute(PetDogItem.MODEL_MODEL) class DogItemMenu{} @UxMenu("萌猫商品")@UxRoute(PetCatItem.MODEL_MODEL) class CatItemMenu{} @UxMenu("宠物品种")@UxRoute(PetType.MODEL_MODEL) class PetTypeMenu{} @UxMenu("萌猫品种")@UxRoute(PetCatType.MODEL_MODEL) class CatTypeMenu{} @UxMenu("宠狗品种")@UxRoute(PetDogType.MODEL_MODEL) class DogTypeMenu{} @UxMenu("宠物达人")@UxRoute(PetTalent.MODEL_MODEL) class PetTalentMenu{} } 图3-5-1-1 菜单注解 Step2 改造现有菜单注解 菜单的层级关系通过@UxMenu的嵌套进行描述 菜单点击效果有三种分别对应不同的Action的类型,关于Action的类型详见3.5.3【Action的类型】一文。 通过@UxRoute定义一个与菜单绑定的viewAction,@UxMenu("创建商店")@UxRoute(value = PetShop.MODEL_MODEL,viewName = "redirectCreatePage",viewType = ViewTypeEnum.FORM),其中viewName代表视图的name(其默认值为redirectListPage,也就是跳转到列表也),value代码视图所属模型的编码,viewType代表view类型(其默认值为ViewTypeEnum.TABLE) @UxServer定义一个与菜单绑定的serverAction,@UxMenu("UxServer")@UxServer(model = PetCatItem.MODEL_MODEL,name = "uxServer") ,其中name代表serverAction的name,model或value代码serverAction所属模型的编码 @UxLink定义一个与菜单绑定的UrlAction,@UxMenu("Oinone官网")@UxLink(value = "http://www.oinone.top”,openType= ActionTargetEnum.OPEN_WINDOW) ,其中value为跳转url,openType为打开方式默认为ActionTargetEnum.ROUTER,打开方式有以下几种 ROUTER("router", "页面路由", "页面路由") DIALOG("dialog", "页面弹窗", "页面弹窗") DRAWER("drawer", "打开抽屉", "打开抽屉") OPEN_WINDOW("openWindow", "打开新窗口", "打开新窗口") 配合菜单演示,PetCatItemAction增加一个uxServer的ServerAction package pro.shushi.pamirs.demo.core.action; ……包引用 @Model.model(PetCatItem.MODEL_MODEL) @Component public class PetCatItemAction extends DataStatusBehavior<PetCatItem> { ……省略其他代码 @Action(displayName = "uxServer") public PetCatItem uxServer(PetCatItem data){ PamirsSession.getMessageHub().info("uxServer"); return data; } } 图3-5-1-2 示例代码 新的菜单初始化代码如下 package pro.shushi.pamirs.demo.core.init; import pro.shushi.pamirs.boot.base.constants.ViewActionConstants; import pro.shushi.pamirs.boot.base.enmu.ActionTargetEnum; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxLink; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxServer; import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenu; import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenus; import pro.shushi.pamirs.demo.api.model.*; import pro.shushi.pamirs.demo.api.proxy.PetShopProxy; import pro.shushi.pamirs.demo.api.proxy.PetShopProxyA; import pro.shushi.pamirs.demo.api.proxy.PetShopProxyB; import pro.shushi.pamirs.meta.enmu.ViewTypeEnum; @UxMenus public class DemoMenus implements ViewActionConstants { @UxMenu("商店") class ShopMenu{ @UxMenu("UxServer")@UxServer(model = PetCatItem.MODEL_MODEL,name = "uxServer") class ShopSayHelloMenu{ } @UxMenu("创建商店")@UxRoute(value = PetShop.MODEL_MODEL,viewName = "redirectCreatePage",viewType = ViewTypeEnum.FORM) class ShopCreateMenu{ }…

    2024年5月23日
    1.5K00
  • 3.3 Oinone以模型为驱动

    模型(model):Oinone一切从模型出发,是数据及对行为的载体: 是对所需要描述的实体进行必要的简化,并用适当的变现形式或规则把它的主要特征描述出来所得到的系统模仿品。模型由元信息、字段、数据管理器和自定义函数构成; 符合面向对象设计原则包括:封装、继承、多态。 本章会带大家快速而全方位地认识Oinone的模型。会从以下几个维度去对模型展开详细介绍。 构建第一个Model 模型的类型 模型的数据管理器 模型的继承 模型编码生成器 枚举与数据字典 字段序列化方式 字段类型之基础与复合 字段类型之关系与引用

    Oinone 7天入门到精通 2024年5月23日
    1.5K00
  • 4.1.19 框架之网关协议-后端占位符

    在我们日常开发中会有碰到一些特殊场景,需要由前端来传一些如“当前用户Id”、“当前用户code”诸如此类只有后端才知道值的参数,那么后端占位符就是来解决类似问题的。如前端传${currentUserId},后端会自动替换为当前用户Id。 Step1 后端定义占位符 我们新建一个UserPlaceHolder继承AbstractPlaceHolderParser,用namespace来定义一个“currentUserId”的占位符,其对应值由value()决定为“PamirsSession.getUserId().toString()”,active要为真才有效,priority为优先级 package pro.shushi.pamirs.demo.core.placeholder; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.user.api.AbstractPlaceHolderParser; @Component public class UserPlaceHolder extends AbstractPlaceHolderParser { @Override protected String value() { return PamirsSession.getUserId().toString(); } @Override public Integer priority() { return 10; } @Override public Boolean active() { return Boolean.TRUE; } @Override public String namespace() { return "currentUserId"; } } 图4-1-19-1 后端定义占位符 Step2 前端使用后端占位符 我们经常在o2m和m2m中会设置domain来过滤数据,这里案例就是在field中设置来过滤条件,domain="createUid == $#{currentUserId}",注意这里用的是$#{currentUserId} 而不是${currentUserId},这是前端为了区分真正变量和后端占位符,提交的时候会把#过滤掉提交。修改宠物达人表格视图的Template中search部分 <template slot="search" cols="4"> <field data="name" label="达人"/> <field data="petTalentSex" multi="true" label="达人性别"/> <field data="creater" /> <!– <field data="petShops" label="宠物商店" domain="createUid == ${activeRecord.creater.id}"/>–> <field data="petShops" label="宠物商店" domain="createUid == $#{currentUserId}"/> <field data="dataStatus" label="数据状态" multi="true"> <options> <option name="DRAFT" displayName="草稿" value="DRAFT" state="ACTIVE"/> <option name="NOT_ENABLED" displayName="未启用" value="NOT_ENABLED" state="ACTIVE"/> <option name="ENABLED" displayName="已启用" value="ENABLED" state="ACTIVE"/> <option name="DISABLED" displayName="已禁用" value="DISABLED" state="ACTIVE"/> </options> </field> <field data="createDate" label="创建时间"/> <field data="unStore" /> </template> 图4-1-19-2 前端使用后端占位符 Step3 重启看效果 请求上都带上了createUid==${currentUserId} 图4-1-19-3 示例效果

    2024年5月23日
    1.7K00
  • 4.1.2 模块之启动指令

    针对不同启动指令的组合可以满足不同场景需求,下面列举了几个常规组合方式,小伙伴们务必把这几种模式都尝试一遍,会更有体感 本节为小伙伴讲解oinone模块的几种启动方式,它是为能灵活地应对企业市场的不同场景需求,为op(本地化部署)、saas和研发提供个性化支撑。也为oinone独特性之单体与分布式的灵活切换提供基础支撑 一、部署参数 参数 名称 默认值 说明 -Plifecycle 生命周期部署指令 RELOAD 可选项:无/INSTALL/PACKAGE/RELOAD/DDL安装-install为AUTO;upgrade为FORCE打包-install为AUTO;upgrade为FORCE;profile为PACKAGE重启-install、upgrade、profile为READONLY打印变更DDL-install为AUTO;upgrade为FORCE;profile为DDL 表4-1-2-1 部署参数 如果在启动命令中配置了部署参数,可不再设置服务参数和可选项参数。下图为在启动命令中添加部署参数的示例。 图4-1-2-1 在启动命令中添加部署参数的示例 二、使用场景 针对不同启动指令的组合可以满足不同场景需求,下面列举了几个常规组合方式,小伙伴们务必把这几种模式都尝试一遍,会更有体感。 场景一:DDL(1)+RELOAD(N)应对专有DBA 因为很多公司数据库是由专门的DBA来管理的,不允许应用直接变更数据库相关配置、表结构、初始化数据。而oinone是基于元数据驱动的,任何模型、行为的变化都会自动转化成对物理存储的改变与元数据变化。 oinone为了适用企业op场景,特别增加了DDL模式。把发布上线分为两个步骤。 一:用DDL模式把涉及到数据库的变更与元数据初始化的脚本进行输出,交由客户公司DBA审批,并执行 二:用RELOAD模式,进行正常的应用重启工作,不进行安装、升级、以及数据库物理变革等操作。 #应用启动关闭自动DDL配置 pamirs.boot.profile: CUSTOMIZE pamirs.boot.options.rebuildTable: false pamirs.persistence.global.auto-create-database: false pamirs.persistence.global.auto-create-table: false 图4-1-2-2 应用启动关闭自动DDL模式 场景二:PACKAGE(1)+RELOAD(N)应对提升多机器实例效率 在机器规模相对大的场景中我们会碰到以下问题: 元数据差量计算、数据库变更、元数据变化保存都非常费时,如果每台机器都来一遍是非常费时费力的 分布式下多机器如果并发进行INSTALL,会导致数据库修改表结构、元数据变化保存锁死 所以我们可以选择一台机器用PACKAGE,其他机器采用RELOAD模式,做到合理规避问题,提升应用发布效率 场景三:INSTALL应对开发模式 研发在本地开发模式下INSTALL是最有效率的,把所需依赖模块一把启动和调试。 上线如果要用INSTALL需要注意,要逐台进行。当然也可以改进成INSTALL(1)+RELOAD(N)模式 三、启动命令解读 查看启动命令 可以在启动日志中查看当前所用启动命令。 图4-1-2-3 在启动日志中查看当前所用启动命令 生命周期管理-Plifecycle 除了通过启动YAML中pamirs.boot属性来设置启动参数,你还可以在应用启动命令中使用-Plifecycle参数来快捷控制模块生命周期的管理方式。该参数的可选项为RELOAD、INSTALL、CUSTOM_INSTALL、PACKAGE、DDL。 java -jar <your jar name>.jar -Plifecycle=RELOAD 启动命令优先级高于YAML中pamirs.boot属性中的install、upgrade和profile属性。如果不使用-Plifecycle参数,则使用YAML中pamirs.boot属性中的install、upgrade和profile属性配置。若YAML中未配置,则采用默认值。 启动配置项 默认值 RELOAD INSTALL CUSTOM_INSTALL PACKAGE DDL install AUTO READONLY AUTO AUTO AUTO AUTO upgrade AUTO READONLY FORCE FORCE FORCE FORCE profile CUSTOMIZE READONLY AUTO CUSTOMIZE PACKAGE DDL 表4-1-2-2 Plifecycle可选项与启动项对应表 profile属性请参考4.1.1【服务启动可选项】一文。只有pamirs.boot.profile=CUSTOMIZE时,在pamirs.boot.options中自定义的可选项才生效。 自动建表-PbuildTable java -jar <your jar name>.jar -PbuildTable=NEVER PbuildTable参数用于设置自动构建表结构的方式。如果不使用该参数,则options属性的默认值请参考4.1.1【服务启动可选项】一文。-PbuildTable参数可选项为: NEVER – 不自动构建表结构,会将pamirs.boot.options中的diffTable和rebuildTable属性设置为false EXTEND – 增量构建表结构,会将pamirs.boot.options中的diffTable属性设置为false,rebuildTable属性设置为true DIFF – 差量构建表结构,会将pamirs.boot.options中的diffTable和rebuildTable属性设置为true 模块在线 -PmoduleOnline java -jar <your jar name>.jar -PmoduleOnline=CHECK PmoduleOnline参数用于设置模块在线的方式。如果不使用该参数,则profile属性的默认值请参考4.1.1【服务启动可选项】一文。-PmoduleOnline参数可选项为: NEVER – 不读取存储在数据库中的模块信息,会将pamirs.boot.options中的reloadModule和checkModule属性设置为false READ – 读取存储在数据库中的模块信息,会将pamirs.boot.options中的checkModule属性设置为false,reloadModule属性设置为true CHECK – 读取存储在数据库中的模块信息并校验依赖模块是否已安装,会将pamirs.boot.options中的reloadModule和checkModule属性设置为true 元数据在线-PmetaOnline java -jar <your jar name>.jar -PmetaOnline=MODULE PmetaOnline参数用于设置元数据在线的方式,如果不使用该参数,则profile属性的默认值请参考4.1.1【服务启动可选项】一文。-PmetaOnline参数可选项为: NEVER – 不持久化元数据,会将pamirs.boot.options中的updateModule、reloadMeta和updateMeta属性设置为false MODULE – 只注册模块信息,会将pamirs.boot.options中的updateModule属性设置为true,reloadMeta和updateMeta属性设置为false ALL – 注册持久化所有元数据,会将pamirs.boot.options中的updateModule、reloadMeta和updateMeta属性设置为true 开放远程服务-PenableRpc PenableRpc参数用于设置是否开启远程服务。如果不使用该参数,则profile属性的默认值请参考4.1.1【服务启动可选项】一文。-PenableRpc参数可选项为true和false。该参数会将参数值设置到pamirs.boot.options中的publishService属性。 开启API服务-PopenApi PopenApi参数用于设置是否开启HTTP API服务。如果不使用该参数,则profile属性的默认值请参考4.1.1【服务启动可选项】一文。-PopenApi参数可选项为true和false。该参数会将参数值设置到pamirs.boot.options中的rebuildHttpApi属性。 开启字段校验-PcheckField PcheckField参数用于设置是否开启字段校验。-PcheckField参数可选项为true和false。由于通常应用的字段数量非常多,会延长系统启动时长,所以默认不会开启字段校验。 启用数据初始化服务-PinitData PinitData参数用于设置是否开启数据初始化服务。如果不使用该参数,则profile属性的默认值请参考4.1.1【服务启动可选项】一文。-PinitData参数可选项为true和false。该参数会将参数值设置到pamirs.boot.options中的updateData属性。 四、不使用自动构建数据库表功能 Oinone LCDP默认提供框架的所有服务,所以会自动构建数据库表。如果不需要使用Oinone的存储构建服务,可以设置YAML文件中关于自动建表的配置。这样就不会动态构建数据库表,你可以手动搭建数据库表。 通过配置启动YAML中pamirs.boot.options.rebuildTable为false彻底关闭自动建表功能。 pamirs: boot: options: rebuildTable: false 图4-1-2-4 不使用自动构建数据库表功能 也可以按需配置启动YAML中pamirs.persistence配置来关闭部分数据源的自动建表功能。persistence配置既可以针对全局也可以分数据源进行配置。 pamirs: persistence: global: # 是否自动创建数据库的全局配置,默认为true autoCreateDatabase: true # 是否自动创建数据表的全局配置,默认为true autoCreateTable: true <your ds key>: # 是否自动创建数据库的数据源配置,默认为true autoCreateDatabase: true # 是否自动创建数据表的数据源配置,默认为true…

    2024年5月23日
    1.1K00

Leave a Reply

登录后才能评论