3.5.3 Action的类型

各类动作我们都碰到过,但都没有展开讲过。这篇文章我们来系统介绍下oinone涉及到的所有Action类型。

一、动作类型

  • 服务器动作ServerAction
  • 类似于Spring MVC的控制器Controller,通过模型编码和动作名称路由,定义存储模型或代理模型将为该模型自动生成动作名称为consturct,queryOne,queryPage,create,update,delete,deleteWithFieldBatch的服务器动作。定义传输模型将为该模型自动生成动作名称为consturct的服务器动作
  • 窗口动作ViewAction
  • 站内跳转,通过模型编码和动作名称路由。系统将为存储模型和代理模型自动生成动作名称为redirectDetailPage的跳转详情页窗口动作,动作名称为redirectListPage的跳转列表页窗口动作,动作名称为redirectCreatePage的跳转新增页窗口动作,动作名称为redirectUpdatePage的跳转更新页窗口动作。
  • 跳转动作UrlAction
  • 外链跳转
  • 客户端动作ClientAction
  • 调用客户端函数

二、默认动作

  • 如果在UI层级,有开放新增语义函数,则会默认生成新增的窗口动作ViewAction,跳转到新增页面
  • 如果在UI层级,有开放更新语义函数,则会默认生成修改的窗口动作ViewAction,跳转到更新页面
  • 如果在UI层级,有开放删除语义函数,则会默认生成删除的客户端动作ClientAction,弹出删除确认对话框

三、第一个服务器动作ServerAction

回顾第一个ServerAction

第一个ServerAction是在3.3.2【模型的类型】一文中的“代理模型”部分出现的,再来看下当时的定义代码

package pro.shushi.pamirs.demo.core.action;

……引用类

@Model.model(PetShopProxy.MODEL_MODEL)
@Component
public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> {

    @Override
    protected PetShopProxy fetchData(PetShopProxy data) {
        return data.queryById();
    }
    @Action(displayName = "启用")
    public PetShopProxy dataStatusEnable(PetShopProxy data){
        data = super.dataStatusEnable(data);
        data.updateById();
        return data;
    }

……其他代码

}

图3-5-3-1 回顾第一个ServerAction

  1. @Action注解将创建服务器动作,并@Model.model绑定

  2. 自定义ServerAction请勿使用get、set、unset开头命名方法或toString命名方法

ServerAction之校验(举例)

Step1 为动作配置校验表达式

使用@Validation注解为PetShopProxyAction的dataStatusEnable服务端动作进行校验表达式配置

package pro.shushi.pamirs.demo.core.action;

……引用类

@Model.model(PetShopProxy.MODEL_MODEL)
@Component
public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> {

    @Override
    protected PetShopProxy fetchData(PetShopProxy data) {
        return data.queryById();
    }
    @Validation(ruleWithTips = {
            @Validation.Rule(value = "!IS_BLANK(data.code)", error = "编码为必填项"),
            @Validation.Rule(value = "LEN(data.shopName) < 128", error = "名称过长,不能超过128位"),
    })
    @Action(displayName = "启用")
    public PetShopProxy dataStatusEnable(PetShopProxy data){
        data = super.dataStatusEnable(data);
        data.updateById();
        return data;
    }

……其他代码

}

图3-5-3-2 为动作配置校验表达式

注:

  1. ruleWithTips可以声明多个校验规则及错误提示;

  2. IS_BLANK和LEN为内置文本函数,更多内置函数详见4.1.12【函数之内置函数与表达式】一文;

  3. 当内置函数不满足时参考4.1.13【Action之校验】一文。

Step2 重启看效果

在商店管理页面点击【启用】得到了预期返回错误信息,显示"编码为必填项"

3.5.3 Action的类型

图3-5-3-3 在商店管理页面点击启用得到了预期返回错误信息

ServerAction之前端展示规则(举例)

既然后端对ServerAction发起提交做了校验,那能不能在前端就不展示呢?当然可以,我们现在就来试下。

Step1 配置PetShopProxyAction的dataStatusEnable的前端出现规则

用注解@Action.Advanced(invisible="!(activeRecord.code !== undefined && !IS_BLANK(activeRecord.code))")来表示,注意这里配对invisible是给前端识别的,所以写法上跟后端的校验有些不一样,但如内置函数IS_BLANK这些是前后端一致实现的,activeRecord在前端用于表示当前记录。

package pro.shushi.pamirs.demo.core.action;

……引用类

@Model.model(PetShopProxy.MODEL_MODEL)
@Component
public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> {

    @Override
    protected PetShopProxy fetchData(PetShopProxy data) {
        return data.queryById();
    }
    @Validation(ruleWithTips = {
            @Validation.Rule(value = "!IS_BLANK(data.code)", error = "编码为必填项"),
            @Validation.Rule(value = "LEN(data.name) < 128", error = "名称过长,不能超过128位"),
    })
    @Action(displayName = "启用")
    @Action.Advanced(invisible="!(activeRecord.code !== undefined && !IS_BLANK(activeRecord.code))")
    public PetShopProxy dataStatusEnable(PetShopProxy data){
        data = super.dataStatusEnable(data);
        data.updateById();
        return data;
    }

……其他代码

}

图3-5-3-4 配置PetShopProxyAction的dataStatusEnable的前端出现规则

Step2 重启看效果

我们发现店铺编码为空的记录,没有了【启用】的操作按钮

3.5.3 Action的类型

图3-5-3-5 店铺编码为空的记录没有启用操作按钮

ServerAction配置说明

常用配合

  1. contextType设置动作上下文类型

a. SINGLE(默认)——单行,常用于列表页(展示在每行末尾的操作栏中)和表单页(展示在页面上方)

b. BATCH——多行,常用于列表页(展示在表格上方按钮区)

c. SINGLE_AND_BATCH——单行或多行,常用于列表页(展示在表格上方按钮区)

d. CONTEXT_FREE——上下文无关,常用于列表页(展示在表格上方按钮区)

  1. bindingType设置按钮所在页面类型(以下仅说明常用类型,详见ViewTypeEnum)

a. TABLE——列表页

b. GALLERY——画廊

c. FORM——表单页

d. DETAIL——详情页

e. CUSTOM——自定义页

注解大全

@Action
├── displayName 显示名称
├── summary 摘要摘要
├── contextType 动作上下文,可选项详见ActionContextTypeEnum
├── bindingType 所在页面类型,可选项详见ViewTypeEnum
├── Advanced 更多配置
│ ├── name 技术名称,默认Java方法名
│ ├── args 参数,默认java参数
│ ├── type 方法类型,默认UPDATE,可选项详见FunctionTypeEnum
│ ├── language 方法实现语言,默认JAVA,可选项详见FunctionLanguageEnum
│ └── invisible 隐藏规则
│ └── bindingView 绑定特定视图
│ └── priority 展示顺序

四、第一个窗口动作ViewAction

回顾第一个ViewAction

第一个ViewAction是在3.3.2【模型的类型】一文中的“传输模型”部分出现的,再来看下当时的定义代码。

package pro.shushi.pamirs.demo.core.init;

……引用类

@Component
public class DemoModuleMetaDataEditor implements MetaDataEditor {
    @Override
    public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) {
        InitializationUtil util = InitializationUtil.get(metaMap, DemoModule.MODULE_MODULE,DemoModule.MODULE_NAME);
        if(util==null){
            return;
        }
        ……其他代码
        //初始化自定义前端行为
        viewActionInit(util);
        ……其他代码
    }
    private void viewActionInit(InitializationUtil util){
        util.createViewAction("demo_petShop_batch_update","批量更新数据状态", PetShopProxy.MODEL_MODEL,
                InitializationUtil.getOptions(ViewTypeEnum.TABLE), PetShopBatchUpdate.MODEL_MODEL,ViewTypeEnum.FORM, ActionContextTypeEnum.SINGLE_AND_BATCH
                , ActionTargetEnum.DIALOG,null,null);
    }
     ……其他代码
}

图3-5-3-6 回顾第一个ViewAction

createViewAction参数详解(建议更换为注解方式)

参数名 类型 说明
name String 技术名称,唯一建要求
displayName String 展示名称
originModel String ViewAction的绑定模型
originViewTypes List ViewAction在绑定模型的哪些视图类型上展示
targetModel String ViewAction跳转到的目标模型
targetViewType ViewTypeEnum ViewAction跳转到的目标模型的什么类型视图
contextType ActionContextTypeEnum ViewAction在绑定模型视图上设置展示的动作上下文类型SINGLE(默认)——单行,常用于列表页(展示在每行末尾的操作栏中)和表单页(展示在页面上方)BATCH——多行,常用于列表页(展示在表格上方按钮区)SINGLE_AND_BATCH——单行或多行,常用于列表页(展示在表格上方按钮区)CONTEXT_FREE——上下文无关,常用于列表页(展示在表格上方按钮区)
pageTarget ActionTargetEnum 页面打开方式 ROUTER:页面路由 DIALOG:页面弹窗 OPEN_WINDOW:打开新窗口DRAWER:打开抽屉
resViewName String ViewAction跳转到的目标模型的指定视图的名称,该视图的类型需要跟targetViewType一致
title String 页面标题
非createViewAction方法的参数,但是ViewAction模型有的属性,可在调用createViewAction以后,在通过setXX属性方法来设置对应属性字段值
priority Intget 展示顺序
bindingView String 绑定特定视图
invisible String 隐藏规则
load String 对于特殊模型的窗口动作进行定制化的加载方式
filter String 代表后端过滤,是一定会加上的过滤条件,用户无感知
domain String 代表前端过滤,是默认会加上的过滤条件,用户可以去除该搜索条件

表3-5-3-1 createViewAction参数详解

注解见:@UxRouteButton(action = @UxAction(),value = @UxRoute())

ViewAction高级参数-Load(下个版本支持)

一般用于以下场景:

  1. 对于特殊模型的窗口动作进行定制化的加载方式。

  2. 不同模型间跳转时,可根据上一个模型的数据内容加载另一个模型的数据内容。

使用初始化工具类设置Load函数:

util.modifyViewAction(TestModele.MODEL_MODEL, InitializationUtil.DEFAULT_CREATE,
        viewAction -> viewAction.setLoad("createPageLoad"));

图3-5-3-7 使用初始化工具类设置Load函数

注:

所示修改窗口动作方法将TestModel模型的默认创建页的加载函数从construct函数改为了createPageLoad函数。

替代方案:构建模型子类,通过子类来重写construct方法

ViewAction高级参数filter和domain(举例)

filter当前版本支持,domain下个版本支持,之所以放一起讲是因为这是过滤的两种形态。

  1. filter代表后端过滤,是一定会加上的过滤条件,用户无感知

  2. domain代表前端过滤,是默认会加上的过滤条件,用户可以去除该搜索条件

Step1 修改自定义pet_talent_table.xml查询条件增加name字段

<view name="tableView" model="demo.PetTalent" cols="1" type="TABLE" enableSequence="true" layout="petTalentTableLayout">
    ……其他代码
    <template slot="search"  cols="4">
        <field data="name" label="达人"/>
        <field data="dataStatus" label="数据状态">
            <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="创建时间"/>
    </template>
</view>

图3-5-3-8 修改自定义pet_talent_table.xml查询条件增加name字段

Step2 为宠物达人模型的两个菜单入口分配置filter和domain

把【宠物达人】菜单调整为三个菜单【宠物达人1】、【宠物达人2】、【宠物达人3】分别设置filter和domain

  1. 修改以菜单【宠物达人1】为入口的ViewAction的filter:

    1. @UxRoute(filter = "name =like= \'老\'")字符串要符合RSQL
  2. 修改以菜单【宠狗达人2】为入口的ViewAction的domain:

    1. @UxRoute(domain = "name =like= \'老\'") 字符串要符合RSQL
  3. 修改以菜单【宠狗达人3】跟函数结合,设置时间默认过滤条件:

    1. @UxRoute(domain = "createDate =ge= \'${ADD_DAY(NOW_STR(),-7)}\' and createDate =lt= \'${NOW_STR()}\'") 字符串要符合RSQL

    2. createDate =ge= \'${ADD_DAY(NOW_STR(),-7)}\' and createDate =lt= \'${NOW_STR()}\'

      1. 用到函数需要用${}装饰;

      2. 更多函数详见4.1.12【函数之内置函数与表达式】一文。

  4. domain的操作符需要跟页面搜索字段定义的操作符一致,比如name字符串字段搜索默认操作符是=like=,如果配置成其他则无效。

@UxMenu("宠物达人1")@UxRoute(value = PetTalent.MODEL_MODEL,filter = "name =like= '老'") class PetTalentMenu{}
@UxMenu("宠物达人2")@UxRoute(value = PetTalent.MODEL_MODEL,domain = "name =like= '老'") class PetTalent2Menu{}
@UxMenu("宠物达人3")@UxRoute(value = PetTalent.MODEL_MODEL,domain = "createDate =ge= '${ADD_DAY(NOW_STR(),-7)}' and createDate =lt= '${NOW_STR()}'") class PetTalent3Menu{}

图3-5-3-9 两个菜单入口分配置filter和domain

Step3 重启看效果

因为【宠物达人1】对应ViewAction加的是Filter,所以只能看到达人名称带“老”字的记录

3.5.3 Action的类型

图3-5-3-10 宠物达人1搜索记录

因为【宠物达人2】对应ViewAction加的是Domain,前端搜索栏里【达人】字段搜索条件为“老”字,达人名称带“老”字的记录,可以手工删除再次搜索全部数据

3.5.3 Action的类型

图3-5-3-11 宠物达人2搜索记录

因为【宠物达人3】对应ViewAction加的是Domain,前端搜索栏里【创建时间】字段带近7天过滤条件,可以手工删除再次搜索全部数据

3.5.3 Action的类型

图3-5-3-12 宠物达人3搜索记录

五、第一个跳转动作UrlAction

回顾第一个UrlAction

在3.5.1【构建第一个Menu】一文中用@UxMenu.url定义了一个百度的菜单,该菜单背后就是一个普通的UrlAction

@UxMenu("Oinone官网")@UxLink(value = "http://www.oinone.top",openType= ActionTargetEnum.OPEN_WINDOW) class SsLink{}

图3-5-3-13 回顾第一个UrlAction

URL计算表达式(暂不支持)

@UxMenu("百度") @UxLink(value = "http://www.baidu.com?wd=${activeRecord.technicalName}",openType= ActionTargetEnum.OPEN_WINDOW) class BaiduLink{}

图3-5-3-14 URL计算表达式

Compute函数(暂不支持)

@UxMenu("百度") @UxLink(value = "http://www.baidu.com",openType= ActionTargetEnum.OPEN_WINDOW,context = {@Prop(name="wd",value= "activeRecord.technicalName"),compute="computeSearchUrl"}) class BaiduLink{}

@Model.model(PetTalent.MODEL_MODEL)
public class PetTalentAction {

    @Function(openLevel = FunctionOpenEnum.API, summary = "计算搜索Url")
    @Function.Advanced(type = FunctionTypeEnum.QUERY)
    public String computeSearchUrl(TestModel data) {
        return "https://www.baidu.com/s?wd=" + data.getName();
    }
}

图3-5-3-15 Compute函数

注:

  1. 代码所示创建链接动作含义为:以新标签页方式跳转至指定URL,该URL值来自于调用后端computeSearchUrl方法的返回值。

六、第一个客户端动作ClientAction

基础客户端动作(举例)

给批量修改店铺状态弹出页面增加一个【自定义返回】和【自定义关闭】按钮,这里注意contextType只能配置为“ActionContextTypeEnum.SINGLE”,因为Form默认只展示contextType为SINGLE的Action

Step1 在PetShopBatchUpdateAction增加@UxClientButton注解

@Model.model(PetShopBatchUpdate.MODEL_MODEL)
@UxClientButton(action = @UxAction(name = "demo_back_test", label = "自定义返回",contextType = ActionContextTypeEnum.SINGLE,bindingType = ViewTypeEnum.FORM),value = @UxClient(value = "$$internal_GotoListTableRouter"))
@UxClientButton(action = @UxAction(name = "demo_close_test", label = "自定义关闭",contextType = ActionContextTypeEnum.SINGLE,bindingType = ViewTypeEnum.FORM),value = @UxClient(value = "$$internal_DialogCancel"))
@Component
public class PetShopBatchUpdateAction {
}

图3-5-3-16 在PetShopBatchUpdateAction增加@UxClientButton注解

Step2 重启看效果

3.5.3 Action的类型

图3-5-3-17 示例效果

前端动作之组合动作(举例)

通过自定义View的Template来设置组合动作,同时学习下自定义视图时如何设置需要展示Action,还是以批量修改店铺状态弹出页面为例子,我们只展示一个组合动作按钮【组合动作】,它包含表单校验、提交、关闭并刷新主视图等行为动作

Step1 自定义弹出框View

  1. 在views/demo_core/template路径下增加一个名为pet_shop_batch_update_form.xml文件

  2. 再通过数据库查看默认页面定义,找到base_view表,过滤条件设置为model =\'demo.PetShopBatchUpdate\',我们就看到该模型下对应的所有view,这些是系统根据该模型的ViewAction对应生成的默认视图,找到类型为【表单(type = FORM)】的记录,查看template字段,复制给pet_shop_batch_update_form.xml文件

  3. 把【<template slot="actions" autoFill="true"/>】替换成以下内容

    <template slot="actions" >
        <action actionType="composition" label="组合动作">
            <action actionType="client" name="$$internal_ValidateForm" />
            <action name="conform" />
            <action actionType="client" name="$$internal_GotoListTableRouter" />
        </action>
    </template>

图3-5-3-18 替换

Step2 重启看效果

这个页面只保留了【组合动作】一个按钮,其他没有配置的按钮就会隐藏掉。

3.5.3 Action的类型

图3-5-3-19 未配置的按钮均会被隐藏

平台默认前端动作:

有元数据定义名的建议最好用元数据名,比如平台默认前端导入动作“$$internal_GotoListImportDialog”可以用“internalGotoListImportDialog”来替代,这样的好处是管理一致,比如权限等功能设置.

<!-- <action name="$$internal_GotoListImportDialog" label="导入" /> 可以用下面方式替代,可以用于权限 -->
<action name="internalGotoListImportDialog" label="导入" />

图3-5-3-20 “$$internal_GotoListImportDialog”可用“internalGotoListImportDialog”替代

name 适用场景 功能描述 元数据定义名
$$internal_GotoListTableRouter 通用 返回上一个页面 internalGotoListTableRouter
$$internal_DeleteOne 关系字段表格 删除绑定的表格的选中数据 internalDeleteOne
$$internal_DialogCancel 弹窗 关闭当前弹窗
$$internal_ReloadData 弹窗 刷新当前主视图组件
$$internal_ListInsertOneAndCloseDialog 通用 打开一个创建弹窗
$$internal_GotoM2MListDialog 多对多关系 表格 打开M2M表格的创建弹窗
$$internal_GotoO2MCreateDialog 一对多关系 表格 打开O2M表格的创建弹窗
$$internal_GotoO2MEditDialog 一对多关系 表格 打开O2M表格的编辑弹窗
$$internal_ListInsertOneAndBackToList 表单 校验表单数据,提交到后端,返回表格(新建)
$$internal_ListUpdateOneAndBackToList 表单 校验表单数据,提交到后端,返回表格(更新)
$$internal_ValidateForm 表单 校验当前表单
$$internal_GotoListExportDialog 一般用于表格 数据导出的action internal_GotoListImportDialog
$$internal_GotoListImportDialog 一般用于表格 数据导入的action internalGotoListImportDialog

表3-5-3-2 平台默认前端动作

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

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

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

相关推荐

  • 4.1.23 框架之信息传递

    在4.1.13【Action之校验】、3.4.1【构建第一个Function】等文章中,都用到PamirsSession.getMessageHub()来设置返回消息,基本上都是在传递后端逻辑判断的异常信息,而且在系统报错时也会通过它来返回错误信息,前端接收到错误信息则会以提示框的方式进行错误提示。其实后端除了可以返回错误信息以外,还可以返回调试、告警、成功、信息等级别的信息给前端。但是默认情况下前端只提示错误信息,可以通过前端的统一配置放开提示级别,有点类似后端的日志级别。 一、不同信息类型的举例 Step1 新建PetTypeAction 借用PetType模型的表格页做为信息传递的测试入口,为PetType模型新增一个ServerAction,在代码中对信息的所有类型进行模拟 package pro.shushi.pamirs.demo.core.action; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.model.PetCatItem; import pro.shushi.pamirs.demo.api.model.PetType; import pro.shushi.pamirs.meta.annotation.Action; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.api.dto.common.Message; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.enmu.ActionContextTypeEnum; import pro.shushi.pamirs.meta.enmu.InformationLevelEnum; import pro.shushi.pamirs.meta.enmu.ViewTypeEnum; @Model.model(PetType.MODEL_MODEL) @Component public class PetTypeAction { @Action(displayName = "消息",bindingType = ViewTypeEnum.TABLE,contextType = ActionContextTypeEnum.CONTEXT_FREE) public PetType message(PetType data){ PamirsSession.getMessageHub().info("info1"); PamirsSession.getMessageHub().info("info2"); PamirsSession.getMessageHub().error("error1"); PamirsSession.getMessageHub().error("error2"); PamirsSession.getMessageHub().msg(new Message().msg("success1").setLevel(InformationLevelEnum.SUCCESS)); PamirsSession.getMessageHub().msg(new Message().msg("success2").setLevel(InformationLevelEnum.SUCCESS)); PamirsSession.getMessageHub().msg(new Message().msg("debug1").setLevel(InformationLevelEnum.DEBUG)); PamirsSession.getMessageHub().msg(new Message().msg("debug2").setLevel(InformationLevelEnum.DEBUG)); PamirsSession.getMessageHub().msg(new Message().msg("warn1").setLevel(InformationLevelEnum.WARN)); PamirsSession.getMessageHub().msg(new Message().msg("warn2").setLevel(InformationLevelEnum.WARN)); return data; } } 图4-1-23-1 为PetType模型新增一个ServerAction Step2 (前端)修改提示级别 在项目初始化时使用CLI构建初始化前端工程,在src/middleware有拦截器的默认实现,修改信息提示的默认级别为【ILevel.SUCCESS】 图4-1-23-2(前端)修改提示级别 const DEFAULT_MESSAGE_LEVEL = ILevel.SUCCESS; 图4-1-23-3(前端)修改提示级别 Step3 重启系统看效果 从页面效果中看到已经不在是只提示错误信息。从协议端看错误级别的信息是在errors下,其他级别的信息是在extensions下。 图4-1-23-4 示例效果 图4-1-23-5 系统提示的返回结果 二、MessageHub的其他说明 是实现上看MessageHub是基于GQL协议,前后端都有配套实现。同时前端还提供了订阅MessageHub的信息功能,以满足前端更多交互要求,前端MessageHub提供的订阅能力使用教程详见4.2.2前端高级特性之【框架之MessageHub】一文。

    2024年5月23日
    81900
  • 3.4.3.2 面向切面-拦截器

    一、拦截器 拦截器为平台满足条件的函数以非侵入方式根据优先级扩展函数执行前和执行后的逻辑。 使用方法上的@Hook注解可以标识方法为拦截器。前置扩展点需要实现HookBefore接口;后置扩展点需要实现HookAfter接口。入参包含当前拦截函数定义与该函数的入参。拦截器可以根据函数定义与入参增加处理逻辑。 拦截器分为前置拦截器和后置拦截器,前者的出入参为所拦截函数的入参,后者的出入参为所拦截函数的出参。可以使用@Hook注解或Hook模型的非必填字段module、model、fun、函数类型、active来筛选出对当前拦截方法所需要生效的拦截器。若未配置任何过滤属性,拦截器将对所有函数生效。 根据拦截器的优先级priority属性可以对拦截器的执行顺序进行调整。priority数字越小,越先执行。 二、前置拦截(举例) 增加一个前置拦截,对PetShop的sayHello函数进行前置拦截,修改函数的入参的shopName属性,在其前面增加"hookbefore:"字符串。并查看效果 Step1 新增PetShopSayHelloHookBefore实现HookBefore接口 为run方法增加@Hook注解 配置module={DemoModule.MODULE_MODULE},这里module代表的是执行模块,该Hook只匹配由DemoModule模块为发起入口的请求 配置model={PetShop.MODEL_MODEL},该Hook只匹配PetShop模型 配置fun={"sayHello"},该Hook只匹配函数编码为sayHello的函数 package pro.shushi.pamirs.demo.core.hook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.api.model.PetShop; 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 PetShopSayHelloHookBefore implements HookBefore { @Override @Hook(module = {DemoModule.MODULE_MODULE},model = {PetShop.MODEL_MODEL},fun = {"sayHello"}) public Object run(Function function, Object… args) { if(args!=null && args[0]!=null){ PetShop arg = (PetShop)args[0]; arg.setShopName("hookbefore:"+ arg.getShopName()); } return args; } } 图3-4-3-5 新增PetShopSayHelloHookBefore实现HookBefore接口 Step2 重启查看效果 用graphQL工具Insomnia查看效果,如果访问提示未登陆,则请先登陆。详见3.4.1【构建第一个Function】一文 用 http://127.0.0.1:8090/pamirs/base 访问,结果我们会发现PetShopSayHelloHookBefore不起作用。是因为本次请求是以base模块作为发起模块,而我们用module={DemoModule.MODULE_MODULE}声明了该Hook只匹配由DemoModule模块为发起入口的请求 图3-4-3-6 示例效果 用 http://127.0.0.1:8090/pamirs/demoCore 访问,前端是以模块名作为访问入口不是模块编码这里大家要注意下 图3-4-3-7 示例效果 用 http://127.0.0.1:8090/pamirs/demoCore 访问,更换到petShop的子模型petShopProxy来访问sayHello函数,结果我们发现是没有效果的。因为配置model={PetShop.MODEL_MODEL},该Hook只匹配PetShop模型 三、后置拦截(举例) 增加一个后置拦截,对PetShop的sayHello函数进行后置拦截,修改函数的返回结果的shopName属性,在其后面增加"hookAfter:"字符串。并查看效果 Step1 新增PetShopSayHelloHookAfter实现HookAfter接口 为run方法增加@Hook注解 配置model={PetShop.MODEL_MODEL},该Hook只匹配PetShop模型 配置fun={"sayHello"},该Hook只匹配函数编码为sayHello的函数 package pro.shushi.pamirs.demo.core.hook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.model.PetShop; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.core.faas.HookAfter; import pro.shushi.pamirs.meta.api.dto.fun.Function; @Component public class PetShopSayHelloHookAfter implements HookAfter { @Override @Hook(model = {PetShop.MODEL_MODEL},fun = {"sayHello"}) public Object run(Function function, Object ret) { if (ret == null) { return null; } PetShop result =null; if (ret instanceof Object[]) { Object[] rets = (Object[])((Object[])ret); if (rets.length == 1) { result = (PetShop)rets[0]; } } else { result = (PetShop)ret; } result.setShopName(result.getShopName()+":hookAfter"); return result; } } 3-4-3-8 新增PetShopSayHelloHookAfter实现HookAfter接口 Step2 重启查看效果 用 http://127.0.0.1:8090/pamirs/base 访问,结果我们会发现PetShopSayHelloHookAfter是起作用。PetShopSayHelloHookBefore没有配置模块过滤。 3-4-3-9 示例效果 用访问,结果我们会发现PetShopSayHelloHookAfte和PetShopSayHelloHookBefore同时起作用 3-4-3-10 示例效果 我们会发现HookAfter只对结果做了修改,所以message中可以看到hookbefore,但看不到hookAfter 四、注意点 不管前置拦截器,还是后置拦截器都可以配置多个,根据拦截器的优先级priority属性可以对拦截器的执行顺序进行调整。priority数字越小,越先执行。小伙伴们可以自行尝试 拦截器必须是jar依赖,不然执行会报错。特别是有的小伙伴配置了一个没有过滤条件的拦截器,就要非常小心 模块启动yml文件可以过滤不需要执行的hook,具体配置详见4.1.1【模块之yml文件结构详解】一文 调用入口不是由前端发起而是后端编程中直接调用,默认不会生效,如果要生效请参考4.1.9【函数之元位指令】一文

    2024年5月23日
    1.0K00
  • 4.5.2 研发辅助之SQL优化

    Oinone体系中是不需要针对模型写SQL的,默认提供了通用的数据管理器。在带来便利的情况下,也导致传统的sql审查就没办法开展。但是我们可以以技术的手段收集慢SQL和限制问题SQL执行。 慢SQL搜集目的:去发现非原则性问题的慢SQL,并进行整改 限制问题SQL执行:对应一些不规范的SQL系统上直接做限制,如果有特殊情况手动放开 一、发现慢SQL 这个功能并没有直接加入到oinone的版本中,需要业务自行写插件,插件代码如下。大家可以根据实际情况进行改造比如: 堆栈入口,例子中只是放了pamirs,可以根据实际情况改成业务包路径 对慢SQL的定义是5s还是3s,根据实际情况变 package pro.shushi.pamirs.demo.core.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; @Intercepts({ @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}) }) @Component @Slf4j public class SlowSQLAnalysisInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); Object result = invocation.proceed(); long end = System.currentTimeMillis(); if (end – start > 10000) {//大于10秒 try { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); StringBuffer slowLog = new StringBuffer(); slowLog.append(System.lineSeparator()); for (StackTraceElement element : stackTraceElements) { if (element.getClassName().indexOf("pamirs") > 0) { slowLog.append(element.getClassName()).append(":").append(element.getMethodName()).append(":").append(element.getLineNumber()).append(System.lineSeparator()); } } Object parameter = null; if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); Configuration configuration = mappedStatement.getConfiguration(); String originalSql = showSql(configuration, boundSql); originalSql = originalSql.replaceAll("\'", "").replace("\"", ""); log.warn("检测到的慢SQL为:" + originalSql); log.warn("业务慢SQL入口为:" + slowLog.toString()); } catch (Throwable e1) { //忽略 } } return result; } public String showSql(Configuration configuration, BoundSql boundSql) { Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); String sql = boundSql.getSql().replaceAll("[\\s]+", " "); if (parameterMappings.size() > 0 && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry…

  • 6.6 消息

    在我们系统研发过程中经常需要发送短信、邮件、站内信等,笔者在本文给大家介绍下如何使用Oinone的消息模块。 准备工作 如果通过我们工程脚手架工具生成的则已经引入了无需做更多的配置,如果不是则需要按以下步骤先配置依赖和增加启动模块 pamirs-demo-boot的pom文件中引入pamirs-message-core包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-message-core</artifactId> </dependency> pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加message,即在启动应用中增加message模块 pamirs: boot: modules: – message 消息参数设置 发送邮件和短信需要设置对应的发送邮箱服务器和短信云,短信目前默认阿里云短信。我们通过代码示例来完成对应邮箱和短信的参数设置 Step1 增加pamirs-message-api依赖 pamirs-demo-core的pom文件中引入pamirs-message-api包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-message-api</artifactId> </dependency> Step2 消息参数设置 请自行替换邮箱服务器和短信通道的账号信息 package pro.shushi.pamirs.demo.core.init; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.api.init.InstallDataInit; import pro.shushi.pamirs.boot.common.api.init.UpgradeDataInit; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.message.enmu.EmailSendSecurityEnum; import pro.shushi.pamirs.message.enmu.MessageEngineTypeEnum; import pro.shushi.pamirs.message.enmu.SMSChannelEnum; import pro.shushi.pamirs.message.model.EmailSenderSource; import pro.shushi.pamirs.message.model.SmsChannelConfig; import java.util.Collections; import java.util.List; @Component public class DemoMessageInit implements InstallDataInit, UpgradeDataInit { private void initEmail(){ EmailSenderSource emailSenderSource = new EmailSenderSource(); emailSenderSource.setName("邮件发送服务"); emailSenderSource.setType(MessageEngineTypeEnum.EMAIL_SEND); //优先级 emailSenderSource.setSequence(10); //发送账号 FIXME 自行替换 emailSenderSource.setSmtpUser(""); //发送密码 FIXME 自行替换 emailSenderSource.setSmtpPassword(""); //发送服务器地址和端口 emailSenderSource.setSmtpHost("smtp.exmail.qq.com"); emailSenderSource.setSmtpPort(465); //" None: SMTP 对话用明文完成。" + //" TLS (STARTTLS): SMTP对话的开始时要求TLS 加密 (建议)" + //" SSL/TLS: SMTP对话通过专用端口用 SSL/TLS 加密 (默认是: 465)") emailSenderSource.setSmtpSecurity(EmailSendSecurityEnum.SSL); emailSenderSource.createOrUpdate(); } private void initSms(){ SmsChannelConfig smsChannelConfig = new SmsChannelConfig(); smsChannelConfig.setType(MessageEngineTypeEnum.SMS_SEND); smsChannelConfig.setChannel(SMSChannelEnum.ALIYUN); //短信签名名称 smsChannelConfig.setSignName("oinone"); //阿里云账号信息 FIXME 自行替换 smsChannelConfig.setAccessKeyId(""); smsChannelConfig.setAccessKeySecret(""); smsChannelConfig.setEndpoint("https://dysmsapi.aliyuncs.com"); smsChannelConfig.setRegionId("cn-hangzhou"); smsChannelConfig.setTimeZone("GMT"); smsChannelConfig.setSignatureMethod("HMAC-SHA1"); smsChannelConfig.setSignatureVersion("1.0"); smsChannelConfig.setVersion("2017-05-25"); smsChannelConfig.createOrUpdate(); //初始化短信模版 //目前支持阿里云短信通道:获取短信模板,如没有短信模板,需要先创建模板,并审核通过 SmsTemplate smsTemplate = new SmsTemplate(); smsTemplate.setName("通知短信"); smsTemplate.setTemplateType(SMSTemplateTypeEnum.NOTIFY); smsTemplate.setTemplateCode("SMS_244595482");//从阿里云获取,自行提供 FIXME smsTemplate.setTemplateContent("尊敬的&{name},你的&{itemName}库存为&{quantity}"); smsTemplate.setChannel(SMSChannelEnum.ALIYUN); smsTemplate.setStatus(SMSTemplateStatusEnum.SUCCESS); smsTemplate.createOrUpdate(); } @Override public boolean init(AppLifecycleCommand command, String version) { initEmail(); initSms(); return Boolean.TRUE; } @Override public boolean upgrade(AppLifecycleCommand command, String version, String existVersion) { initEmail(); initSms(); return Boolean.TRUE; } @Override public List<String> modules() { return Collections.singletonList(DemoModule.MODULE_MODULE);…

    2024年5月23日
    1.1K00
  • 5.5 基础支撑之结算域

    一、基础介绍 随着企业的业务不断进行数字化改造、业务越来越在线化,给企业财务工作带来几个明显的变化和挑战: 变化: 业务在线后,不同类收费、预售、授信模式的创新层出不穷,需要财务不仅只从事单一传统的会计核算工作,还需要积极地参与到业务中去。 从事后算账事后报账,变成财务业务一体化信息的实时处理 挑战: 业务系统与财务系统明显割裂,业务部门与财务部门各自采用一套软件处理其数据,不能及时沟通信息和协同更正信息。 财务系统往往都是单体的传统架构,凭证处理能力无法适应今天企业的不断爆棚的业务发展。 财务的严谨性与业务的灵活性中间有巨大的鸿沟,导致业务要做一种创新的模式,财务可能是最大阻碍。 不论是传统软件公司喜欢说的业财一体化还是互联网平台公司喜欢说的结算平台,都是为了解决以上变化和挑战的。业财一体化主要是从财务部门角度出发进行,在业务支撑上化被动为主动。结算中心往往是结合财务部门和业务运营部门的需求。如果拿我们下面介绍的,计费、账务、会计三个领域来说,业财一体化项目往往只包括账务和会计,结算中心往往包括:计费、账务、会计。或者说业财一体化弱化了计费,没有纳入企业统一管理,把如何计价给到了业务系统自行决定或者简单处理只要产生应收应付单据(计费详单)就好了。 结算域的是一个相对比较专业的领域,没有一定背景知识甚至连一些专业名词都很难理解,更不用说模型设计了,这里我尽快地简单去描述定位而不是描述细节。而且2.1.9版本的结算领域相对还是没有那么完善,这里介绍的是下个版本的内容,所以大家看当前版本的时候会有一些对不上。 二、子领域职责 图5-5-1 子领域职责 计费 计费的价值 随着企业多业务发展以及融合计费需求,我们需要引入计费模型,对灵活计价模式进行支持,快速支撑未来可能的计费方式等 计费的核心设计理念 所有的计算器都继承自虚函数计算器y=f(x) 平滑兼容-默认斜率计算器y=a+bxY – 求值结果(用下标描述结果是什么)A – 偏移量(计算固定值)B – 斜率(费率值)X – 变量(数量)任何计算都是通过一组斜率组合出来的 利用区间限定定义各种斜率组合出各种算法交易额0-100w:y=0.03x >100w:y=0.02x;时间0:00-6:00:y=0.02x 6:00-24:00:y=0.03xX- 变量,数量 图5-5-2 计费的核心设计理念 更灵活多维区间组合,时间维度、计数器维度、其它属性维度计数器区间斜率限定,比如交易额、空间、使用月份数… 计费的核心功能 通过产品定义运营方案 通过订购产品完成商务合同的签订来决定客户计费策略,或者通过系统产品定义通用计费策略 支撑各类产品的模拟计费 以事件驱动,根据事件、产品、订购关系完成产品路由,并实时产生计费详单 根据计费科目与账务科目,打通账务进行核销 账务 账务的价值 以账户账本为中心,提供记账、账户管理,以及账务的实时监控与持续对账。如果计费是对接业务,那么账务的价值是对接财务系统 账务的核心设计理念 不依赖计费,可独立对接,所有业务最终都需要反馈到帐户账本的操作上,并通过账本明细记录所有操作 账务的核心功能 记账:充值、转账、提现,冻结、解冻,差错处理 账务管理:开户、科目维护 账务查询:对账 会计(暂不在计划内) 会计的价值 结算平台的会计模块不是严格意义上的会计系统,它主要是衔接其他的财务系统,做凭证前置处理。在于汇总凭证,产出业务帐,对接到财务总帐系统,缓解财务系统压力。 三、模型介绍 图5-5-3 模型介绍 四、结算基础流程 图5-5-4 结算基础流程

    2024年5月23日
    78600

Leave a Reply

登录后才能评论