4.1.11 函数之异步执行

异步任务是非常常见的一种开发模式,它在分布式的开发模式中有很多应用场景如:

  1. 高并发场景中,我们一般采用把长流程切短,用异步方式去掉可以异步的非关键功能,缩小主流程响应时间,提升用户体验

  2. 异构系统的集成调用,通过异步任务完成解耦与自动重试

  3. 分布式系统最终一致性的可选方案

今天我们了解oinone是如何结合Spring+TbSchedule来完成异步任务

一、TbSchedule介绍

它是一个支持分布式的调度框架,让批量任务或者不断变化的任务能够被动态的分配到多个主机的JVM中,在不同的线程组中并行执行,所有的任务能够被不重复,不遗漏的快速处理。基于ZooKeeper的纯Java实现,由Alibaba开源。在互联网和电商领域TBSchedule的使用非常广泛,目前被应用于阿里巴巴、淘宝、支付宝、京东、聚美、汽车之家、国美等很多互联网企业的流程调度系统。也是笔者早期在阿里参与设计的一款产品。

oinone的异步任务执行原理(如下图4-1-11-1所示),先做一个大致了解:

4.1.11 函数之异步执行

图4-1-11-1 Oinone的异步任务执行原理图

基础管理工具

下载tbSchedule的控制台jar包去除文件后缀.txt(详见本书籍【附件一】)pamirs-middleware-schedule-console-3.0.1.jar.txt(31.2 MB)

启动控制台


java -jar pamirs-middleware-schedule-console-3.0.1.jar 

图4-1-11-2 控制台启动方式

访问地址


http://127.0.0.1:10014/schedule/index.jsp?manager=true

图4-1-11-3 访问地址

配置zk连接参数

image.png

图4-1-11-4 配置zk连接参数

oinone默认实现任务类型

image.png

图4-1-11-5 Oinone默认实现任务类型

  • baseScheduleNoTransactionTask
  • baseScheduleTask
  • remoteScheduleTask --- 适用于pamirs-middleware-schedule独立部署场景
  • serialBaseScheduleNoTransactionTask
  • serialBaseScheduleTask
  • serialRemoteScheduleTask --- 适用于pamirs-middleware-schedule独立部署场景
  • cycleScheduleNoTransactionTask
  • delayMsgTransferScheduleTask
  • deleteTransferScheduleTask

注:

a. 默认情况下:所有任务的任务项都只配置了一个任务项0,只有一台机器能分配任务。

1. 如果要修改配置可以在启动项目中放置schedule.json,来修改配置

2. 人工进入控制修改任务对应任务项的配置

b. 如果想为某一个核心任务配置的独立调度器,不受其他任务执行影响。那么见独立调度的异步任务

任务表相关说明

4.1.11 函数之异步执行

图4-1-11-6 任务表相关说明

二、构建第一个异步任务(举例)

Step1 新建PetShopService和PetShopServiceImpl

1 新建PetShopService定义updatePetShops方法

package pro.shushi.pamirs.demo.api.service;

import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;

import java.util.List;

@Fun(PetShopService.FUN_NAMESPACE)
public interface PetShopService {
    String FUN_NAMESPACE = "demo.PetShop.PetShopService";
    @Function
    void updatePetShops(List<PetShop> petShops);

}

图4-1-11-7 新建PetShopService定义updatePetShops方法

  1. PetShopServiceImpl实现PetShopService接口并在updatePetShops增加@XAsync注解

    1. displayName = "异步批量更新宠物商店",定义异步任务展示名称

    2. limitRetryNumber = 3,定义任务失败重试次数,,默认:-1不断重试

    3. nextRetryTimeValue = 60,定义任务失败重试的时间数,默认:3

    4. nextRetryTimeUnit,定义任务失败重试的时间单位,默认:TimeUnitEnum.SECOND

    5. delayTime,定义任务延迟执行的时间数,默认:0

    6. delayTimeUnit,定义任务延迟执行的时间单位,默认:TimeUnitEnum.SECOND

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

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.demo.api.service.PetShopService;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.trigger.annotation.XAsync;

import java.util.List;

@Fun(PetShopService.FUN_NAMESPACE)
@Component
public class PetShopServiceImpl implements PetShopService {

    @Override
    @Function
    @XAsync(displayName = "异步批量更新宠物商店",limitRetryNumber = 3,nextRetryTimeValue = 60)
    public void updatePetShops(List<PetShop> petShops) {
        new PetShop().updateBatch(petShops);
    }
}

图4-1-11-8 实现PetShopService接口并在updatePetShops增加@XAsync注解

Step2 修改PetShopBatchUpdateAction的conform方法

  1. 引入PetShopService

  2. 修改conform方法

    1. 利用ArgUtils进行参数转化,ArgUtils会经常用到。

    2. 调用petShopService.updatePetShops方法

package pro.shushi.pamirs.demo.core.action;
…… 引依赖类
@Model.model(PetShopBatchUpdate.MODEL_MODEL)
@Component
public class PetShopBatchUpdateAction {

    @Autowired
    private PetShopService petShopService;
    ……其他代码
    @Action(displayName = "确定",bindingType = ViewTypeEnum.FORM,contextType = ActionContextTypeEnum.SINGLE)
    public PetShopBatchUpdate conform(PetShopBatchUpdate data){
        if(data.getPetShopList() == null || data.getPetShopList().size()==0){
            throw  PamirsException.construct(DemoExpEnumerate.PET_SHOP_BATCH_UPDATE_SHOPLIST_IS_NULL).errThrow();
        }
        List<PetShopProxy> proxyList = data.getPetShopList();
        for(PetShopProxy petShopProxy:proxyList){
            petShopProxy.setDataStatus(data.getDataStatus());
        }
        Tx.build(new TxConfig().setPropagation(Propagation.REQUIRED.value())).executeWithoutResult(status -> {
            new PetShopProxy().updateBatch(proxyList);
            //利用ArgUtils进行参数转化
            List<PetShop> shops = ArgUtils.convert(PetShopProxy.MODEL_MODEL, PetShop.MODEL_MODEL,proxyList);
            petShopService.updatePetShops(shops);
//            throw PamirsException.construct(DemoExpEnumerate.SYSTEM_ERROR).errThrow();
        });
        return data;
    }

}

图4-1-11-9 修改PetShopBatchUpdateAction的conform方法

Step3 重启看效果

异步会有一定的延迟,我们按以下步骤测试下异步执行效果

  1. 进入商店管理列表页,选择中一行数据点击批量更新数据状态按钮进入批量修改宠物商店数据状态页面:

image.png

图4-1-11-10 进入批量修改宠物商店数据状态页面

  1. 在批量修改宠物商店数据状态页面,数据状态设置为未启用,点击组合动作按钮回到商店管理列表页:

image.png

图4-1-11-11 点击组合动作按钮回到商店管理列表页

  1. 查看商店管理列表页的数据记录的数据状态字段是否修改成功,此时可能未修改成功,也可能已经修改成功,因为本身就是毫秒级的速度,点击搜索刷新数据,发现数据记录的数据状态字段修改成功:

image.png

图4-1-11-12 发现数据记录的数据状态字段修改成功

  1. 查看任务表,根据任务表与日期的对照关系查询指定表

image.png

图4-1-11-13 根据任务表与日期的对照关系查询指定表

三、异步任务高级玩法

顺序异步任务(举例)

这里的顺序任务是指把任务按一定规则分组以后按时间顺序串行执行,不同分组间的任务不相互影响。有点类似mq的顺序消息

eg:订单的状态变更的异步任务需要根据任务产生时间顺序执行。那么分组规则是按订单id分组,执行顺序按任务产生顺序执行

Step1 PetShopService和PetShopServiceImpl

  1. 修改PetShopService新增定义asyncSerialUpdatePetShops方法
package pro.shushi.pamirs.demo.api.service;

import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;

import java.util.List;

@Fun(PetShopService.FUN_NAMESPACE)
public interface PetShopService {
    String FUN_NAMESPACE = "demo.PetShop.PetShopService";
    @Function
    void updatePetShops(List<PetShop> petShops);
    @Function
    void asyncSerialUpdatePetShops(List<PetShop> petShops);

}

图4-1-11-14 修改PetShopService新增定义asyncSerialUpdatePetShops方法

  1. 修改PetShopServiceImpl实现ScheduleAction接口,并增加asyncSerialUpdatePetShops方法

    1. 引入executeTaskActionService用于提交异步串行任务ExecuteTaskAction

      1.setExecuteNamespace(getInterfaceName()),确保跟getInterfaceName()一致

      2.setExecuteFun("execute");跟执行函数名“execute”一致

      3.setTaskType(TaskType.SERIAL_BASE_SCHEDULE_NO_TRANSACTION_TASK.getValue()),必须用SERIAL_BASE_SCHEDULE_NO_TRANSACTION_TASK,其为顺序执行任务类型

      4.setBizId(petShop.getCreateUid())//根据创建人Id分组,根据实际业务情况决定

    2. getInterfaceName()跟函数的命名空间保持一致

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.enmu.TimeUnitEnum;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.demo.api.service.PetShopService;
import pro.shushi.pamirs.framework.connectors.data.tx.interceptor.PamirsTransactional;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.util.JsonUtils;
import pro.shushi.pamirs.middleware.schedule.api.ScheduleAction;
import pro.shushi.pamirs.middleware.schedule.common.Result;
import pro.shushi.pamirs.middleware.schedule.domain.ScheduleItem;
import pro.shushi.pamirs.middleware.schedule.eunmeration.TaskType;
import pro.shushi.pamirs.trigger.annotation.XAsync;
import pro.shushi.pamirs.trigger.model.ExecuteTaskAction;
import pro.shushi.pamirs.trigger.service.ExecuteTaskActionService;

import java.util.List;

@Fun(PetShopService.FUN_NAMESPACE)
@Component
public class PetShopServiceImpl implements PetShopService, ScheduleAction {

    @Autowired
    private ExecuteTaskActionService executeTaskActionService;

    @Override
    @Function
    @XAsync(displayName = "异步批量更新宠物商店",limitRetryNumber = 3,nextRetryTimeValue = 60)
    public void updatePetShops(List<PetShop> petShops) {
        new PetShop().updateBatch(petShops);
    }

    @PamirsTransactional
    @Override
    @Function
    public void asyncSerialUpdatePetShops(List<PetShop> petShops){
        for(PetShop petShop:petShops) {
            executeTaskActionService.submit((ExecuteTaskAction) new ExecuteTaskAction()
                    .setBizId(petShop.getCreateUid())//根据创建人Id分组,根据实际业务情况决定
                    .setTaskType(TaskType.SERIAL_BASE_SCHEDULE_NO_TRANSACTION_TASK.getValue())
                    .setNextRetryTimeUnit(TimeUnitEnum.SECOND)//失败重试时间单位
                    .setNextRetryTimeValue(10)//失败重试时间数
                    .setLimitRetryNumber(6)//最多重试次数
                    .setDisplayName("异步顺序任务-更新宠物商店,以createUid分组")
                    .setExecuteNamespace(getInterfaceName())
                    .setExecuteFun("execute")
                    .setContext(JsonUtils.toJSONString(petShop)));
        }
    }

    @Override
    public String getInterfaceName() {
        return PetShopService.FUN_NAMESPACE;
    }

    @Override
    @Function
    public Result<Void> execute(ScheduleItem scheduleItem) {
        Result<Void> result = new Result<>();
        PetShop petShop = JsonUtils.parseObject(scheduleItem.getContext(),PetShop.class);
        petShop.updateById();
        return result;
    }
}

图4-1-11-15 代码示例

Step2 修改PetShopBatchUpdateAction的conform方法

改调用异步顺序方法,petShopService.asyncSerialUpdatePetShops(shops)

package pro.shushi.pamirs.demo.core.action;
…… 引依赖类
@Model.model(PetShopBatchUpdate.MODEL_MODEL)
@Component
public class PetShopBatchUpdateAction {

    @Autowired
    private PetShopService petShopService;
    ……其他代码
    @Action(displayName = "确定",bindingType = ViewTypeEnum.FORM,contextType = ActionContextTypeEnum.SINGLE)
    public PetShopBatchUpdate conform(PetShopBatchUpdate data){
        if(data.getDataStatus() == null){
            throw  PamirsException.construct(DemoExpEnumerate.PET_SHOP_BATCH_UPDATE_DATASTATUS_IS_NULL).errThrow();
        }
        List<PetShopProxy> proxyList = data.getPetShopList();
        for(PetShopProxy petShopProxy:proxyList){
            petShopProxy.setDataStatus(data.getDataStatus());
        }
        Tx.build(new TxConfig().setPropagation(Propagation.REQUIRED.value())).executeWithoutResult(status -> {
            //利用ArgUtils进行参数转化
            List<PetShop> shops = ArgUtils.convert(PetShopProxy.MODEL_MODEL, PetShop.MODEL_MODEL,proxyList);
//            petShopService.updatePetShops(shops);
            petShopService.asyncSerialUpdatePetShops(shops);
//            throw PamirsException.construct(DemoExpEnumerate.SYSTEM_ERROR).errThrow();
        });
        return data;
    }

}

图4-1-11-16 修改PetShopBatchUpdateAction的conform方法

Step3 重启看效果

页面效果跟构建第一个异步任务一样,但任务生产和执行逻辑不一样。会根据biz_id分配任务项与分组确保执行顺序

  1. 分配任务项,相同任务项一定会分配给同一个schedule的执行者

  2. 分组,任务在同一个schedule的执行者,相同分组Id一定会分配给同一个线程执行

页面操作完以后查看数据任务表

image.png

图4-1-11-17 根据biz_id分配任务项与分组确保执行顺序

独立调度的异步任务(举例)

如果把所有任务都放在同一个任务类型下,复用同一套任务策略、任务配置、任务执行器。那么当某些不重要的异步任务大量失败会影响其他任务的执行,所以我们在一些高并发大任务量的场景下会独立给一些核心异步任务配置独立调度策略。

Step1 修改pamirs-demo-core的pom

增加对pamirs-middleware-schedule-core依赖,为了复用oinone默认实现任务类型的基础逻辑,在例子中我们自定义的异步任务继承SerialBaseScheduleNoTransactionTask的基础逻辑

        <dependency>
            <groupId>pro.shushi.pamirs.middleware.schedule</groupId>
            <artifactId>pamirs-middleware-schedule-core</artifactId>
        </dependency>

图4-1-11-18 增加pamirs-middleware-schedule-core依赖

Step2 新建PetShopUpdateCustomAsyncTask

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

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.middleware.schedule.core.tasks.SerialBaseScheduleNoTransactionTask;

@Component
public class PetShopUpdateCustomAsyncTask extends SerialBaseScheduleNoTransactionTask {

    public static final String TASK_TYPE = PetShopUpdateCustomAsyncTask.class.getSimpleName();

    @Override
    public String getTaskType() {
        return TASK_TYPE;
    }

}

图4-1-11-19 新建PetShopUpdateCustomAsyncTask

Step3 修改PetShopServiceImpl的asyncSerialUpdatePetShops方法

修改TaskType为PetShopUpdateCustomAsyncTask.TASK_TYPE

    @PamirsTransactional
    @Override
    @Function
    public void asyncSerialUpdatePetShops(List<PetShop> petShops){
        for(PetShop petShop:petShops) {
            executeTaskActionService.submit((ExecuteTaskAction) new ExecuteTaskAction()
                    .setBizId(petShop.getCreateUid())//根据创建人Id分组,根据实际业务情况决定
//                    .setTaskType(TaskType.SERIAL_BASE_SCHEDULE_NO_TRANSACTION_TASK.getValue())
                    .setTaskType(PetShopUpdateCustomAsyncTask.TASK_TYPE)
                    .setNextRetryTimeUnit(TimeUnitEnum.SECOND)//失败重试时间单位
                    .setNextRetryTimeValue(10)//失败重试时间数
                    .setLimitRetryNumber(6)//最多重试次数
                    .setDisplayName("异步顺序任务-更新宠物商店,以createUid分组")
                    .setExecuteNamespace(getInterfaceName())
                    .setExecuteFun("execute")
                    .setContext(JsonUtils.toJSONString(petShop)));
        }
    }

图4-1-11-20 修改TaskType为PetShopUpdateCustomAsyncTask.TASK_TYPE

Step4 初始化数据

下载以下文件放在pamirs-demo-boot的src/main/resources/init目录下

schedule.json.txt(8 KB)

我们在系统原有提供的schedule.json,中增入任务类型为petShopUpdateCustomAsyncTask的配置,配置项"taskType": "CUSTOM",标志为客户自定义。实际注册到TbSchedule会按beanNames转化为taskType,其他参数含义见TbSchedule的管理控制台有对应中文说明


  {
    "taskType": "CUSTOM",
    "beanNames": "petShopUpdateCustomAsyncTask",
    "values": {
      "heartBeatRate": 1000, 
      "judgeDeadInterval": 10000,
      "sleepTimeNoData": 500,
      "sleepTimeInterval": 500,
      "fetchDataNumber": 500,
      "executeNumber": 1,
      "threadNumber": 8,
      "processorType": "SLEEP",
      "expireOwnSignInterval": 1.0,
      "taskParameter": "",
      "taskKind": "static",
      "taskItems": [
        0,1,2,3,4,5,6,7
      ],
      "maxTaskItemsOfOneThreadGroup": 0,
      "version": 0,
      "sts": "resume",
      "fetchDataCountEachSchedule": -1
    },
    "strategy": {
      "IPList": [
        "127.0.0.1"
      ],
      "numOfSingleServer": 1,
      "assignNum": 4,
      "kind": "Schedule",
      "taskParameter": "",
      "sts": "resume"
    }
  }

图4-1-11-21 增加任务类型为petShopUpdateCustomAsyncTask的配置

Step5 重启看效果

页面效果跟构建第一个异步任务一样,但任务生产和执行逻辑不一样。会根据biz_id分配任务项与分组确保执行顺序,同时会有独立的调度器以及规则配置

  1. 在tbSchedule的管理控制台,可以看见多了一个“petShopUpdateCustomAsyncTask”的任务类型,点编辑就可以看到我们配置任务类型对应的参数

image.png

图4-1-11-22 tbSchedule管理控制台

  1. 页面操作完以后查看对应数据任务表

image.png

图4-1-11-23 查看对应数据任务表

四、不同应用如何隔离执行单元

在schedule跟模块部署一起的时候,多模块独立boot的情况下,需要做必要的配置。如果schedule独立部署则没有必要,因为全部走远程,不存在类找不到的问题

  1. 通过配置pamirs.zookeeper.rootPath,确保两组机器都能覆盖所有任务分片,这样不会漏数据

  2. 通过pamirs.event.schedule.ownSign来隔离。确保两组机器只取各自产生的数据,这样不会重复执行数据

pamirs:
    zookeeper:
    zkConnectString: 127.0.0.1:2181
    zkSessionTimeout: 60000
    rootPath: /demo
    event:
    enabled: true
    schedule:
      enabled: true
      ownSign: demo
    rocket-mq:
      namesrv-addr: 127.0.0.1:9876

图4-1-11-24 配置pamirs.zookeeper.rootPath

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

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

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

相关推荐

  • 6.1 文件与导入导出(改)

    导入导出在一定程度上是企业级软件和效率工具(office工具)的桥梁 文件的上传下载以及业务数据的导入导出是企业级软件一个比较常规的需求,甚至是巨量的需求。业务有管理需要一般都伴随有导入导出需求,导入导出在一定程度上是企业级软件和效率工具(office工具)的桥梁。oinone的文件模块就提供了通用的导入导出实现方案,以简单、一致、可扩展为目标,简单是快速入门,一致是用户操作感知一致、可扩展是满足用户最大化的自定义需求。 下图为文件导入导出的实现示意图,大家可以做一个整体了解 图6-1-1 文件导入导出实现示意图 一、基础能力 准备工作 pamirs-demo-api的pom文件中引入pamirs-file2-api包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-file2-api</artifactId> </dependency> 图6-1-2 引入pamirs-file2-api包依赖 DemoModule增加对FileModule的依赖 @Module(dependencies = {FileModule.MODULE_MODULE}) 图6-1-3 DemoModule增加对FileModule的依赖 pamirs-demo-boot的pom文件中引入pamirs-file2-core包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-file2-core</artifactId> </dependency> 图6-1-4 启动工程引入pamirs-file2-core包依赖 pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加file,即在启动模块中增加file模块 pamirs: boot: modules: – file 图6-1-5 pamirs-demo-boot的application-dev.yml文件中增加配置 pamirs-demo-boot的application-dev.yml文件中增加oss的配置。更多有关文件相关配置详见4.1.1【模块之yml文件结构详解】一文 cdn: oss: name: 阿里云 type: OSS bucket: demo uploadUrl: #换成自己的oss上传服务地址 downUrl: #换成自己的oss下载服务地址 accessKeyId: #阿里云oss的accessKeyId accessKeySecret: #阿里云oss的accessKeySecret mairDir: upload/demo #换成自己的目录 validTime: 360000 timeout: 60000 active: true referer: www.shushi.pro 图6-1-6 application-dev.yml文件中增加oss的配置 其他文件系统支持 a. 文件系统类型 类型 服务 OSS 阿里云 UPYUN 又拍云 MINIO Minio HUAWEI_OBS 华为云 LOCAL 本地文件存储 表6-1-1 支持的文件系统类型 b. OSS配置示例 ⅰ. 华为云OBS cdn: oss: name: 华为云 type: HUAWEI_OBS bucket: pamirs #(根据实际情况修改) uploadUrl: obs.cn-east-2.myhuaweicloud.com downloadUrl: obs.cn-east-2.myhuaweicloud.com accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true allowedOrigin: http://192.168.95.31:8888,https://xxxx.xxxxx.com referer: 图6-1-7 OBS的配置说明 ⅱ. MINIO cdn: oss: name: minio type: MINIO bucket: pamirs #(根据实际情况修改) uploadUrl: http://192.168.243.6:32190 #(根据实际情况修改) downloadUrl: http://192.168.243.6:9000 #(根据实际情况修改) accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: 图6-1-8 MINIO的配置说明 ⅲ. 又拍云 cdn: oss: name: 又拍云 type: UPYUN bucket: pamirs #(根据实际情况修改) uploadUrl: v0.api.upyun.com downloadUrl: v0.api.upyun.com accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000…

    2024年5月23日
    1.3K00
  • 3.3.6 枚举与数据字典

    枚举是大家在系统开发中经常用的一种类型,在oinone中也对枚举类型进行了支持,同时也做了相应的加强。希望通过本文能让大家对枚举的使用,有更全面的认知 一、枚举系统与数据字典 枚举是列举出一个有穷序列集的所有成员的程序。在元数据中,我们使用数据字典进行描述。 协议约定 枚举需要实现IEnum接口和使用@Dict注解进行配置,通过配置@Dict注解的dictionary属性来设置数据字典的唯一编码。前端使用枚举的displayName来展示,枚举的name来进行交互;后端使用枚举的value来进行交互(包括默认值设置也使用枚举的value)。 枚举会存储在元数据的数据字典表中。枚举分为两类:1.异常类;2.业务类。异常类枚举用于定义程序中的错误提示,业务类枚举用于定义业务中某个字段值的有穷有序集。 编程式用法 图3-3-6-1 编程式用法 如果一个字段的类型被定义为枚举,则该字段就可以使用该枚举来进行可选项约束(options)。该字段的可选项为枚举所对应数据字典的子集。 可继承枚举 继承BaseEnum可以实现java不支持的继承枚举。同时可继承枚举也可以用编程式动态创建枚举项。 可继承枚举也可以兼容无代码枚举。 图3-3-6-2 可继承枚举 二进制枚举 可以通过@Dict注解设置数据字典的bit属性或者实现BitEnum接口来标识该枚举值为2的次幂。 二、enum不可继承枚举(举例) 我们在介绍抽象基类中AbstractDemoCodeModel和AbstractDemoIdModel就引入了数据状态(DataStatusEnum)字段,并设置了必填和默认值为DISABLED。DataStatusEnum实现了IEnum接口,并用@Dict(dictionary = DataStatusEnum.dictionary, displayName = "数据状态")进行了注解。为什么不能继承呢?因为JAVA语言的限制导致enum是不可继承的 package pro.shushi.pamirs.core.common.enmu; import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.IEnum; @Dict(dictionary = DataStatusEnum.dictionary, displayName = "数据状态") public enum DataStatusEnum implements IEnum<String> { DRAFT("DRAFT", "草稿", "草稿"), NOT_ENABLED("NOT_ENABLED", "未启用", "未启用"), ENABLED("ENABLED", "已启用", "已启用"), DISABLED("DISABLED", "已禁用", "已禁用"); public static final String dictionary = "partner.DataStatusEnum"; private String value; private String displayName; private String help; DataStatusEnum(String value, String displayName, String help) { this.value = value; this.displayName = displayName; this.help = help; } public String getValue() { return value; } public String getDisplayName() { return displayName; } public String getHelp() { return help; } } 图3-3-6-3 不可继承枚举 三、BaseEnum可继承枚举(举例) Step1 新增CatShapeExEnum继承CatShapeEnum枚举 package pro.shushi.pamirs.demo.api.enumeration; import pro.shushi.pamirs.meta.annotation.Dict; @Dict(dictionary = CatShapeExEnum.DICTIONARY,displayName = "萌猫体型Ex") public class CatShapeExEnum extends CatShapeEnum { public static final String DICTIONARY ="demo.CatShapeExEnum"; public final static CatShapeExEnum MID =create("MID",3,"中","中"); } 图3-3-6-4 新增CatShapeExEnum继承CatShapeEnum枚举 Step2 修改PetCatType的shape字段类型为CatShapeExEnum package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.enumeration.CatShapeExEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.MultiTableInherited(type = PetCatType.KIND_CAT) @Model.model(PetCatType.MODEL_MODEL) @Model(displayName="萌猫品种",labelFields = {"name"}) public class PetCatType extends PetType { public static final String MODEL_MODEL="demo.PetCatType"; public static final String KIND_CAT="CAT"; @Field(displayName = "宠物分类",defaultValue…

    2024年5月23日
    3.3K00
  • 3.4 Oinone以函数为内在

    函数(Function):是oinone可管理的执行逻辑单元,跟模型绑定则对应模型的方法 描述满足数学领域函数定义,含有三个要素:定义域A、值域C{f(x),x属于A}和对应法则f。其中核心是对应法则f,它是函数关系的本质特征 满足面向对象原则,可设置不同开放级别,本地与远程智能切换。 本章会带大家更加详细地了解Function的方方面面,主要以几下几个维度 构建第一个Function 函数的开放级别与类型 函数的相关特性 函数元数据详解

    Oinone 7天入门到精通 2024年5月23日
    1.5K00
  • 3.6 问题排查工具

    当前端发起对应用的访问时,如果出现错误,那么我们可以通过以下方式进行简易排查,如果排查不出来,则也可以把排查工具给出的信息发送给Oinone官方售后进行进一步分析。本文将通过模拟异常信息,来介绍排查工具,提供了哪些辅助信息帮我们来快速定位问题。 排查工具基础介绍 通过前端页面的 /debug 路由路径访问调试工具的页面,假设我们的前端页面访问地址为http://localhost:6800,那么我们的排查工具请求路径就是 http://localhost:6800/debug排查工具可以帮我们排查前端页面元数据异常和后端接口的异常 排查前端页面元数据 将问题页面浏览器地址栏内 page 后的部分复制到调试工具的 debug 路由后重新发起请求,如图可以看到调试工具展示的信息,可以根据这些信息排查问题。 排查后端接口 后端接口出现问题后,打开(在原页面)浏览器的调试工具,切换到“网络”的标签页,在左侧的历史请求列表中找到需要调试的请求,右键会弹出菜单,点击菜单中的 “复制”,再次展开该菜单,点击二级菜单中的“以 fetch 格式复制”,这样可以复制到调试所需要的信息 2.复制调试信息到“接口调试”标签页内的文本框内,点击“发起请求”按钮获取调试结果 我们可以看到页面展示了该接口的各种调试信息,我们可以据此排查问题。 场景化的排查思路 业务代码中存在代码bug 报错后发起调试请求,我们可以看到,调试工具直接给出了异常抛出的具体代码所在位置,此时再切换到“全部堆栈”下,可以看到是业务类的233行导致的空指针异常,查看代码后分析可得是data.getName().eqauls方法在调用前未做条件判断补全该判断后代码可以正常执行 业务代码中没有直接的错误,异常在平台代码中抛出 报错后发起调试请求可以看到异常不在业务代码内再切换到“全部堆栈”,可以看到具体异常信息,提示core_demo_item表出现了重复的主键,该表是DemoItem模型的我们还可以切换到“sql调试”的标签页,可以看到出错的具体sql语句经过分析可以得知是240行的data.create()�重复创建数据导致的。 三、排查工具无法定位怎么办 当我们通过排查工具还是没有定位到问题的时候,可以通过调试页面的“下载全部调试数据”和“下载调试数据”按钮将调试信息的数据发送给官方售后人员帮助我们定位排查问题。 点击页面最顶部的“下载全部调试数据”按钮,可以下载页面调试数据和接口调试数据点击“调试接口”标签页内的“下载调试数据”按钮,可以下载接口调试数据 四、排查工具细节

    2024年5月23日
    1.8K00
  • 4.4 Oinone的分布式体验进阶

    在分布式开发中,每个人基本只负责自己相关的模块开发。所以每个研发就都需要一个环境,比如一般公司会有(N个)项目环境、1个日常环境、1个预发环境、1个线上环境。在整项目环境的时候就特别麻烦,oinone的好处是在于每个研发可以通过boot工程把需要涉及的模块都启动在一个jvm中进行开发,并不依赖任何环境,在项目开发中,特别方便。但当公司系统膨胀到一定规模,大到很多人都不知道有哪些模块,或者公司出于安全策略考虑,或者因为启动速度的原因(毕竟模块多了启动的速度也会降下来)。本文就给大家介绍oinone与经典分布式组织模式的兼容性 一、模块启动的最小集 我们来改造SecondModule模块,让该模块的用户权限相关都远程走DemoModule Step1 修改SecondModule的启动工程application-dev.yml文件 除了base、second_core两个模块保留,其他模块都去除了。 pamirs: boot: init: true sync: true modules: – base – second_core 图4-4-1 SecondModule的application-dev.yml仅配置两个模块 Step2 去除boot工程的依赖 去除SecondModule启动工程的pom依赖 <!– <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-resource-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-user-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-auth-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-message-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-international</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-business-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-apps-core</artifactId> </dependency> –> 图4-4-2 去除boot工程多余的依赖 Step3 重启SecondModule 这【远程模型】和【远程代理】均能访问正常 图4-4-3 远程模型和远程代理菜单均能访问正常 Step4 SecondModule增加对模块依赖 我们让SecondModule增加用户和权限模块的依赖,期待效果是:SecondModule会对用户和权限的访问都会走Dome应用,因为Demo模块的启动工程中包含了user、auth模块。 修改pamirs-second-api的pom文件增加对user和auth的api包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-user-api</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-auth-api</artifactId> </dependency> 图4-4-4 修改pamirs-second-api的pom文件 修改SecondModule类,增加依赖定义 @Module( dependencies = {ModuleConstants.MODULE_BASE, AuthModule.MODULE_MODULE, UserModule.MODULE_MODULE} ) 图4-4-5 配置SecondModule的依赖 Step5 修改RemoteTestModel模型 为RemoteTestModel模型增加user字段 @Field.many2one @Field(displayName = "用户") private PamirsUser user; 图4-4-6 为RemoteTestModel模型增加user字段 Step6 重启系统看效果 mvn install pamirs-second工程,因为需要让pamirs-demo工程能依赖到最新的pamirs-second-api包 重启pamirs-second和pamirs-demo 两个页面都正常 图4-4-7 示例效果一 图4-4-8 示例效果二 二、PmetaOnline的NEVER指令(开发时环境共享) 我们在4.1.2【模块之启动指令】一文中介绍过 “-PmetaOnline指令”,该参数用于设置元数据在线的方式,如果不使用该参数,则profile属性的默认值请参考服务启动可选项。-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 oinone的默认模式下元数据都是注册持久化到DB的,但当我们在分布式场景下新开发模块或者对已有模块进行本地化开发时,做为开发阶段我们肯定是希望复用原有环境,但不对原有环境照成影响。那么-PmetaOnline就很有意义。让我们还没有经过开发自测的代码产生的元数据仅限于开发本地环境,而不是直接影响整个大的项目环境 PmetaOnline指令设置为NEVER(举例) Step1 为DemoCore新增一个DevModel模型 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.model(DevModel.MODEL_MODEL) @Model(displayName = "开发阶段模型",summary="开发阶段模型,当PmetaOnline指令设置为NEVER时,本地正常启动但元数据不落库",labelFields={"name"}) public class DevModel extends AbstractDemoCodeModel{ public static final String MODEL_MODEL="demo.DevModel"; @Field(displayName = "名称") private String name; } 图4-4-9 为DemoCore新增一个DevModel模型 Step2 为DevModel模型配置菜单 @UxMenu("开发模型")@UxRoute(DevModel.MODEL_MODEL) class DevModelProxyMenu{} 图4-4-10 为DevModel模型配置菜单 Step3 启动Demo应用时指定-PmetaOnline 图4-4-11 启动Demo应用时指定-PmetaOnline Step4 重启系统看效果 查看元数据 图4-4-12 DB查看元数据变化 菜单与页面能正常操作 图4-4-13 开发模型菜单可正常操作 图4-4-14 开发模型详情页面可正常操作 Step5 Never模式需注意的事项 业务库需设定为本地开发库,这样才不会影响公共环境,因为对库表结构的修改还是会正常进行的 如果不小心影响了公共环境,需要对公共环境进行重启恢复 系统新产生的元数据(如:例子中的【开发模式】菜单)不受权限管控 三、分布式开发约定 设计约定 跨模块的存储模型间继承,在部署时需要跟依赖模块配置相同数据源。这个涉及模块规划问题,比如业务上的user扩展模块,需要跟user模块一起部署。…

    2024年5月23日
    1.4K00

Leave a Reply

登录后才能评论