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日

相关推荐

  • 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日
    96200
  • 3.5.2.1 整体介绍

    虽然我们有小眼睛可以让用户自定义展示字段和排序喜好,以及通过权限控制行、列展示,但在我们日常业务开发中还是会对页面进行调整,以满足业务方的对交互友好和便捷性的要求。本节会在如何自定义之前我们先介绍页面结构与逻辑,再带小伙伴一起完成自定义view的Template和Layout,以及整个母版的Template和Layout 页面的构成讲解 页面交互拓扑图 页面交互拓扑图 图3-5-2-1 页面交互拓扑图 注:页面逻辑交互拓扑图说明 模块作为主切换入口 模块决定菜单列表 菜单切换触发点击action 前端根据Mask、View进行渲染, a. Mask是母版是确定了主题、非主内容分发区域所使用组件和主内容分发区域联动方式的页面模板。全局、应用、视图动作、视图都可以通过mask属性指定母版 bMask和View都是有layout定义和template定义合并而成,系统会提供默认母版,以及为每种视图提供默认layout c. layout与template通过插槽进行匹配 Action根据不同类型做出不同访问后端服务、url跳转、页面路由、发起客户端动作等 Aciton路由可以指定Mask、视图组件的layout、template a. 当layout没有指定的时候则用系统默认的 b. 当template没有指定的时候,且视图组件相同类型有多条记录时,根据优先级选取 Mask和视图组件的layout优先级(视图组件>视图动作 > 应用 > 全局) 默认母版以及各类视图组件 母版布局 默认母版基础布局base-layout <mask layout="default"> <header slot="header"/> <container slot="main" name="main"> <sidebar slot="sidebar"/> <container slot="content"/> </container> <footer slot="footer"/> </mask> 图3-5-2-2 默认母版基础布局base-layout 母版template <mask layout="default"> <mask name="defaultMask"> <template slot="header"> <container name="appBar"> <element widget="logo"/> <element widget="appFinder"/> </container> <container name="operationBar"> <element widget="notification"/> <element widget="dividerVertical"/> <element widget="languages"/> </container> <element widget="userProfile"/> </template> <template slot="sidebar"> <element widget="navMenu"/> </template> <template slot="content"> <element widget="breadcrumb"/> <element widget="mainView"/> </template> </mask> 图3-5-2-3 母版template 注: 上例中因为名称为main的插槽不需要设置更多的属性,所以在template中缺省了main插槽的template标签。 最终可执行视图 <mask name="defaultMask"> <header> <container name="appBar"> <element widget="logo"/> <element widget="appFinder"/> </container> <container name="operationBar"> <element widget="notification"/> <element widget="dividerVertical"/> <element widget="languages"/> </container> <element widget="userProfile"/> </header> <container name="main"> <sidebar name="sidebar"> <element widget="navMenu"/> </sidebar> <container name="content"> <element widget="breadcrumb"/> <element widget="mainView"/> </container> </container> <footer/> </mask> 图3-5-2-4 最终可执行视图 表格视图布局 默认表格视图基础布局base-layout <view type="table"> <view type="search"> <element widget="search" slot="search"> <xslot name="fields" slotSupport="field" /> </element> </view> <pack widget="fieldset"> <element widget="actionBar" slot="actions" slotSupport="action" /> <element widget="table" slot="table"> <xslot name="fields" slotSupport="field" /> <element widget="actionsColumn" slot="actionsColumn"> <xslot name="rowActions" slotSupport="action" /> </element> </element> </pack> </view> 图3-5-2-5 默认表格视图基础布局base-layout 注:table标签的子标签为column组件,如果field填充到元数据插槽fields没有column组件将自动包裹column组件。 表格视图template <view type="table" model="xxx" name="tableViewExample">…

    2024年5月23日
    1.3K00
  • 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.4K00
  • 企业个性化配置

    平台除了帮助企业快速解决业务需求外,还提供了企业文化、风格个性化能力,本章节详细介绍个性化配置。包括:登陆页配置、企业形象配置、系统风格配置。 系统配置 配置入口 配置入口:右上角登陆账号——系统配置——全局配置——登陆页配置、企业形象配置、系统风格配置。 登陆页配置 用户自定义登陆页的主题,一共有六个主题可选择,分别为: 图一:左侧是图片,右侧是登录输入框,Logo在左上角; 图二:右左侧是图片,右侧是登录输入框,Logo在左上角; 图三:底图是图片,登录输入框浮在页面左侧,Logo在左上角; 图四:底图是图片,登录输入框浮在页面中间,Logo在左上角; 图五:底图是图片,登录输入框浮在页面右侧,Logo在左上角; 图六:底图是图片,登录输入框浮在页面中间,Logo在登录框上方。 页面背景 上传登录页背景,支持上传jpg、png格式的图片或mp4、mov、avi格式视频。尺寸建议为1920*1080px,文件大小不超过50MB。 登录页logo:指配置登录页左上角的logo图标。 预览:配置后可在右侧电脑中点击全屏预览效果。 配置示例 企业形象配置 企业形象配置:可设置企业信息、业务应用导航栏logo、浏览器logo,设置完成之后,点击发布之后设置生效。 系统风格配置 系统风格配置,可设置主题(浅色、深色)、尺寸(大、中、小),配置后可全屏预览,发布后即可更换成对应的风格。 下载代码可进行自定义风格。

    2024年6月20日
    1.2K00
  • 2.1 数字化时代软件业的另一个本质变化

    随着企业从信息化向数字化转变,软件公司提供的产品也由传统的企业管理软件向企业商业支撑软件发展。这一变化带来了许多技术上的挑战和机遇。在之前的章节中,我们提到企业的视角已经从内部管理转向业务在线和生态在线协同,这也带来了一系列新的需求。但是,我们常常会忽视这一变化所带来的对系统要求的变化。在本章中,我们将探讨这些技术上的变化,以及这些变化所带来的机遇和挑战。 图2-1 从信息化到数字化软件本质变化 在信息化时代,企业的业务围绕着内部管理效率展开,借鉴国外优秀的管理经验,企业将其管理流程固化下来,典型的例子是ERP项目。这类项目上线后往往长期稳定,不轻易更改,因此信息化时代软件的技术流派侧重于通过模型对业务进行全面支持。例如,SAP具有丰富的配置能力,将已有企业管理思想抽象到极致。其功能基本上可以通过配置来实现,因此其模型设计特别复杂。但是,我们也应该清楚地了解到,配置是面向已知问题的。在数字化时代,创新和业务迭代速度非常快,这种方法可能就不太适合了。我们知道,模型抽象是在设计时具有前瞻性的,一旦不适合,修改起来就会异常困难。 随着数字化时代的到来,企业主的关注点已经从单一企业内部管理转变为了围绕企业上下游价值链的协同展开。这种变化给企业信息化系统提出了更高的要求,例如业务需求的响应速度、系统性能和用户体验等方面。现在,企业对软件不仅是管理需求的承载,更是业务在线化的承载。传统的重模型设计软件模式已经不再适用,因为业务本身不断创新和变化。因此,数字化时代需要新的软件技术流派,这种流派必须是轻模型加上低代码技术的结合体。通过模型抽象80%的通用场景,剩余的20%个性化需求可以通过技术手段来完成。这样的设计可以让每家企业的研发人员轻松理解模型,而不像ERP模型那样异常复杂,无法进行修改。此外,配合低代码技术可以快速研发和上线。如果说配置化是面向已知问题的,那么低代码就是面向未知问题设计的。虽然低代码的概念可以追溯到上个世纪80年代,当时是为了满足企业内部部门之间有协同需求,但又没有专业软件支撑,定制化开发又不划算的辅助场景。但现在它的核心原因是企业数字化的核心场景不稳定,变化很快,每家企业都有强烈的个性化需求。因此,低代码成为解决这些问题的核心手段,数字化时代的低代码需要具备处理复杂场景的能力,而不仅仅是围绕着内部管理展开。 企业在数字化转型的过程中需要考虑到不仅是成熟的全链路业务解决方案,还要应对数字化场景的快速变化和持续创新的需求。为此,Oinone打造了一站式低代码商业支撑平台,从业务与技术两个维度来帮助企业建立开放、链接、安全的数字化平台。这将在水平和垂直两个维度上全面推动企业数字化转型。 另外,低代码的另一个好处是完成了软件本身的数字化建设。通过基于元数据设计,元数据成为软件中数据、逻辑和交互的数据,软件结合AI可以有更多的创造可能。想象一下,AI了解软件的元数据后可以自我运作,人在极少情况下才需要参与,人机交互也会发生大的改变。未来的软件交互不再需要研发提前预设,而是能够实现用户所需即所呈现的效果。作为一家帮助企业进行数字化转型的软件公司,请问您的数字化转型是否已经完成呢?

    2024年5月23日
    1.3K00

Leave a Reply

登录后才能评论