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日

相关推荐

  • 1.1 Oinone的萌芽

    在信息化时代,中国并没有涌现出一家世界知名的软件公司。这是因为像SAP、Oracle、IBM、Salesforce、NetSuite、Odoo等西方巨头所拥有的最佳实践在业务、技术和模式方面,给予了它们在企业信息化建设中高额利润的优势。中国软件业在这个时代的角色是学习和追随者,而最优秀的追随者是金蝶和用友,它们能在国家推行会计电算化的机遇中占据领先地位。但是,追随者始终只是追随者,没有真正的突破。 我自己进入软件行业的经历可以追溯到2015年。当时资本市场非常热门,大家都在创业。我认为这是一个时代的机会,就像国家改革开放一样。于是,我和很多同事一起开始了创业之旅。在数式之前,我加入并创办了三家公司:500mi、数列和端点。整个过程给了我宝贵的经验和启示,帮助我找到了最终想要的方向。 在500mi公司时,我从技术岗位转型为业务经营,起步并不顺利。然而,我从这份经历中获得了一堂重要的课:做自己擅长的事情,有助于渡过创业启动期最艰难的阶段。同时,市场调研为我提供了一个信号:传统企业对于IT的需求正逐渐向互联网靠拢。这个信号像注入了一剂强心剂,激励我继续前行。 2016年,我和三个曾在阿里工作的同事一起创办了一家新公司——数列,我们决定专注于我们最擅长的领域,即软件服务商。在没有任何商务资源的情况下,我们第一年就完成了1000多万的合同,这相较之前是一个非常成功的开端。然而,对于公司未来的发展方向,我们花费了长达大半年的时间进行思考:应该坚持做底层的PaaS还是专注于企业可见的上层应用和业务产品?我倾向于后者。尽管我们持续存在分歧,但凭借着多年的革命友情,最终我们友好地分道扬镳。数列此前的成功让我更加坚信:在数字化时代,软件需求将会有井喷式的增长,数字化软件服务将是未来5-10年的重要方向。而在这个领域,专业的技能将是应对未来不确定性的真正力量。 提到数字化,就不得不提阿里巴巴提出的中台理念。中台理念在15年前被阿里巴巴提出,当时引起了广泛的关注和讨论。企业之所以认同中台理念,是因为他们的核心需求已经从内部转向外部:从关注管理、流程、效率的提升,转向关注外部协同、运营、创新。他们已经不再只担心企业的效率和成本,而是担心自己是否有能力跟上时代的快速变化。现今做生意的渠道已经不再是单一的线下渠道,而是包括淘宝、天猫、京东、拼多多、抖音、快手等多个线上渠道,以及海外市场,这种变化速度非常快。而中台的核心理念是敏捷响应、低成本快速创新,正好解决了企业主的核心焦虑。 企业的视角正在从内部管理向业务在线和生态在线(协同)转变,这种转变带来了一系列新的需求(如下图1-1所示)。这种转变不仅是为了支持现有业务的发展,也为企业未来的业务发展和创新提供了支持,并将变化实时反映到上下游合作伙伴中。 图1-1 企业视角转变带来一系列新的诉求 在2017年下半年,阿里云收购了端点科技,打算重启阿里软件。那个时候,市场上涌现出一批中台厂商,整个行业也比较混乱,很多人对互联网架构本身的理解不够深入,快速学习拿到阿里云认证后就开始做定制化的中台架构开发,但最终的效果无法达到预期。因此,阿里云和端点科技的联姻是为了弥补阿里云没有向外输出上层应用产品能力的缺陷。多年来,软件市场一直被国外厂商掌控,中国一直缺乏一个强大的本土软件公司。阿里收购端点,承载着无数中国人的软件梦想。在这种背景下,我回到了阿里体系,加入了端点科技。后来,我参与了许多中台项目,深刻地认识到搭建中台技术架构和一些基础能力,为上层应用场景落地并不难。但是,当客户接手扩展中台能力和新的上层应用场景时,效果往往不尽如人意,这并不是中台架构理念的问题,而是因为传统企业客户的IT能力大多较弱,这是一个硬伤。许多文章都在讲述中台战略,长篇大论地描述组织中台、技术中台、业务中台、数据中台,我们不去评论这些方法论的对错,从技术角度回到初衷,我们只关注一个问题:技术是为商业服务的,中台如何快速满足企业业务多变的需求? 我们经历了多个行业的中台建设,每次都向客户强调第一阶段是打好基础,因此需要较长的周期,并且每个项目都需要顶级架构师来把控整体项目。如何找到互联网架构与传统软件良好结合点,降低对组织的要求,实现中台架构的标准化输出?这是我回归阿里后致力于解决的问题。然而,随着阿里云对端点战略发展思路的变化,阿里不再提供SaaS服务,而只愿意做平台被其他企业集成。因此,我离开了端点,并决定把自己的技术思考转化为现实,于是数式科技诞生了。 在数字化时代,无论是业务、技术还是商业模式的最佳实践,都源自中国。中国已经从追随者转变为互联网领域的全面引领者。我们有理由相信,中国一定会崛起一家世界级的软件公司,而Oinone将始终以此为愿景。

    2024年5月23日
    1.8K00
  • 3.3.5 模型编码生成器

    在我们日常开发中经常要一些单据生成指定格式的编码,而且现在分布式环境下要考虑的事情会特别多。oinone提供了简易的编码生成能力 一、编码生成器 可以在模型或者字段上配置编码自动生成规则。在进行数据存储时,如果配置了编码自动生成规则的字段值为空,则系统将根据规则自动生成编码。编码自动生成功能是通过序列生成器来支持的。可以在序列生成器生成的序列编码基础上再进行组合配置的功能编码生成最终的编码。序列生成器可以配置初始序列,步长,日期格式,长度。 模型序列生成器(举例) 使用模型编码生成器,需要继承CodeModel或者有Code字段,那么使用Model.Code注解即可。 Step1 为PetShop增加一个@Model.Code注解,并增加一个店铺编码(Code)字段 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import java.sql.Time; @Model.model(PetShop.MODEL_MODEL) @Model(displayName = "宠物店铺",summary="宠物店铺",labelFields = {"shopName"}) @Model.Code(sequence = "DATE_ORDERLY_SEQ",prefix = "P",size=6,step=1,initial = 10000,format = "yyyyMMdd") public class PetShop extends AbstractDemoIdModel { public static final String MODEL_MODEL="demo.PetShop"; @Field(displayName = "店铺编码") private String code; @Field(displayName = "店铺名称",required = true) private String shopName; @Field(displayName = "开店时间",required = true) private Time openTime; @Field(displayName = "闭店时间",required = true) private Time closeTime; } 图3-3-5-1 为PetShop增加一个@Model.Code注解 Step2 重启查看效果 进入店铺新增页面新增一个oinone宠物店铺003 图3-3-5-2 示例操作效果图 查看店铺列表页面,新增的记录中店铺编码一列,已经按Model.Code注解要求地生成了 图3-3-5-3 示例操作效果图 Step3 小结 在我们日常开发中经常要一些单据生成指定格式的编码,而且现在分布式环境下要考虑的事情会特别多。oinone提供了简易的编码生成能力,大家可以根据编码注解说明,自行对PetShop模型进行不同配置,来学习编码生成器的知识 字段序列生成器 字段编码生成器,在对应的字段上增加,并使用Field.Sequence注解即可 Step1 为PetShop增加一个字段codeTwo并增加@Field.Sequence注解 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import java.sql.Time; @Model.model(PetShop.MODEL_MODEL) @Model(displayName = "宠物店铺",summary="宠物店铺",labelFields = {"shopName"}) @Model.Code(sequence = "DATE_ORDERLY_SEQ",prefix = "P",size=6,step=1,initial = 10000,format = "yyyyMMdd") public class PetShop extends AbstractDemoIdModel { public static final String MODEL_MODEL="demo.PetShop"; @Field(displayName = "店铺编码") private String code; @Field(displayName = "店铺编码2") @Field.Sequence(sequence = "DATE_ORDERLY_SEQ",prefix = "C",size=6,step=1,initial = 10000,format = "yyyyMMdd") private String codeTwo; @Field(displayName = "店铺名称",required = true) private String shopName; @Field(displayName = "开店时间",required = true) private Time openTime; @Field(displayName = "闭店时间",required = true) private Time closeTime; } 图3-3-5-4 为PetShop增加一个字段codeTwo Step2 重启查看效果 进入店铺新增页面新增一个oinone宠物店铺004 图3-3-5-5 示例操作效果图 查看店铺列表页面,新增的记录中店铺编码2一列,已经按Field.Sequence注解要求地生成了 图3-3-5-6 示例操作效果图 二、编码注解说明,模型更多其他注解详见4.1.6【模型之元数据详解】…

    2024年5月23日
    1.9K00
  • 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.7K00
  • 文件

    文件应用下包含“导入/导出模版、导入任务、导出任务”三个菜单。其中导入/导出任务菜单比较常用。 导入/导出模版 当前版本会为租户的表格视图自动创建导出模版,此处可进行编辑、查看详情的操作。 导入任务 导入任务可以下载导入文档,点击详情可以查看该条记录的导入结果,任务信息分组中可以查看错误信息。 导出任务 和导入任务一致,导出任务菜单中可以下载导出文档,点击详情可以查看该条记录的导出结果,任务信息分组中可以查看错误信息。

    2024年6月20日
    1.2K00
  • 数据编码

    1. 什么是数据编码 当模型中的字段数据需要有一定的编码规定,可以在模型中设计模型或字段的数据编码。 编码预览:实时展示规则设置后的编码。 2. 编码前/后缀 编码前缀:必须以字母开头,且仅支持数字或字母,最多8个字符。 编码后缀:必须以字母开头,且仅支持数字或字母,最多8个字符。 3. 格式化日期 开关默认关闭,即数据编码中不包含日期。开关打开后,默认的日期格式为“年年年年月月日日”,也可以切换成“年年月月日日、年年月月、年年年年、年年”。 序列归零周期:与格式化日期选择有关,若选择为“年年年年月月日日”,则可选“年、月、日”,选择为“年年年年”,则只可选“年”数据编码序列会按照设置的这个周期归零。 4. 编码序列 编码方式:可选择连续序列或非连续序列,选择会影响后续包含哪些设置。 序列长度:序列包含多少位数字,可以设置1 – 18之间的整数。 序列起始值:数据编码序列的起始值,默认值为3。 当编码方式设置为非连续序列时,展示其余两个配置项。 步长类型:默认值为“自定义步长”,也可以设置成“1 – 10之间随机步长”。 步长:当选择“自定义步长”时,设置的步长即为真实步长。当选择“1 – 10之间随机步长”时,实际步长为1 – 设置值之间的随机整数。

    2024年6月20日
    1.8K00

Leave a Reply

登录后才能评论