函数之异步执行

总体介绍

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

  • 高并发场景中,我们一般采用把长流程切短,用异步方式去掉可以异步的非关键功能,缩小主流程响应时间,提升用户体验
  • 异构系统的集成调用,通过异步任务完成解耦与自动重试
  • 分布式系统最终一致性的可选方案

本文将介绍oinone是如何结合Spring+TbSchedule来完成异步任务

构建第一个异步任务

新建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);
}

2、PetShopServiceImpl实现PetShopService接口并在updatePetShops增加@XAsync注解

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);
    }
}

a. displayName = "异步批量更新宠物商店",定义异步任务展示名称
b. limitRetryNumber = 3,定义任务失败重试次数,,默认:-1不断重试
c. nextRetryTimeValue = 60,定义任务失败重试的时间数,默认:3
d. nextRetryTimeUnit,定义任务失败重试的时间单位,默认:TimeUnitEnum.SECOND
e. delayTime,定义任务延迟执行的时间数,默认:0
f. delayTimeUnit,定义任务延迟执行的时间单位,默认:TimeUnitEnum.SECOND

修改PetShopBatchUpdateAction调用异步任务

  1. 引入PetShopService
  2. 修改conform方法,调用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){
            List<PetShop> shops = ArgUtils.convert(PetShopProxy.MODEL_MODEL, PetShop.MODEL_MODEL,proxyList);
           // 调用异步任务
            petShopService.updatePetShops(shops);
        });
        return data;
    }
    }

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

在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

Oinone社区 作者:望闲原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/11480.html

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

Like (0)
望闲's avatar望闲数式管理员
Previous 2024年5月25日 pm8:10
Next 2024年5月27日 pm8:19

相关推荐

  • Oinone离线部署设计器JAR包

    概述 Oinone平台为合作伙伴提供了多种部署方式,这篇文章将介绍如何在私有云环境部署Oinone平台JAR包。 本文以5.2.6版本为例进行介绍。 部署环境要求 包含全部中间件及设计器服务的环境要求 CPU:8 vCPU 内存(RAM):16G以上 硬盘(HDD/SSD):60G以上 仅设计器服务的环境要求 CPU:8 vCPU 内存(RAM):8G以上 硬盘(HDD/SSD):40G以上 部署准备 在部署环境创建部署目录 mkdir -p /home/admin/oinone-designer PS:为方便管理,所有Oinone部署所需文件都应该在该目录下存放。 服务器需要安装的中间件 JDK:jdk_1.8_221版本以上 下载地址 MySQL:8.0.26版本以上 下载地址 Redis:5.0.2版本以上 下载地址 安装教程 Zookeeper:3.5.8版本以上 下载地址 安装教程 Nginx:任意版本(推荐使用源码编译安装方式,并开启rewrite、https等功能模块) Linux安装教程 下载地址 使用Docker启动所有中间件 点击下载一键部署所有中间件套件包 middleware-kits.zip 部署清单 下面列举了文章中在本地环境操作结束后的全部文件: 设计器JAR包:pamirs-designer-boot-v5.2-5.2.6.jar 离线部署结构包:oinone-designer-jar-offline.zip 第三方数据库驱动包(非MySQL数据库必须) PS:如需一次性拷贝所有部署文件到部署环境,可以将文档步骤在本地环境执行后,一次性将所有文件进行传输。 在本地环境准备部署文件 下载离线部署结构包 oinone-designer-jar-offline.zip 下载部署JAR包 5.2.6版本发布日志 查看更多版本 找到独立部署所有设计器JAR标题,下面有对应的JAR包提供下载。 例如:https://oinone-jar.oss-cn-zhangjiakou.aliyuncs.com/install/oinone-designer/pamirs-designer-boot-v5.2-5.2.6.jar 后端服务部署 将部署JAR包移动到backend目录下,并重命名为oinone-designer.jar mv pamirs-designer-boot-v5.2-5.2.6.jar backend/oinone-designer.jar PS:该名称为startup.sh脚本的默认值,可根据实际情况自行修改 将Pamirs许可证移动到backend/config目录下,并重命名为license.lic mv oinone-demo_1730163770607.lic backend/config/license.lic 加载非MySQL数据库驱动(按需) 将驱动jar文件移动到backend/lib目录下即可。 以KDB8数据库驱动kingbase8-8.6.0.jar为例 mv kingbase8-8.6.0.jar backend/lib/ PS:backend/lib目录为非设计器内置包的外部加载目录(外部库),可以添加任何jar包集成到设计器中。 修改backend/startup.sh脚本 IP:修改为可被外部访问的IP地址 DB_BASE_:base库相关数据库连接配置 DB_PAMIRS_:pamirs库相关数据库连接配置 REDIS_:Redis相关配置 MQ_NAME_SERVER:RocketMQ的name-server连接地址 ZOOKEEPER_:Zookeeper相关配置 PS:若需要配置方言或其他参数,可直接修改backend/config/application.yml配置文件,变量仅用于简单配置场景 执行startup.sh脚本启动 sh startup.sh 执行完成后会打印三个路径 后端路径:backend root path: /path/to/backend 前端路径:frontend root path: /path/to/frontend Nginx配置路径:nginx services path: /path/to/nginx Nginx配置 在本地nginx服务中找到nginx.conf,并添加Nginx配置路径为加载目录 http { … include /path/to/nginx/*.conf; } 修改结构包中的default.conf第7行root配置为前端路径到dist目录下 server { … root /path/to/frontend/dist; } 修改结构包中的oss.conf第30行alias配置为前端路径到static目录下 server { … location /static { … alias /path/to/frontend/static; } } 访问服务 使用http://127.0.0.1:9090访问服务

    2024年11月1日
    1.8K00
  • 首次登录修改密码和自定义密码规则等

    场景描述 在某些场景下,可能需要实现 用户首次登录强制修改密码的功能,或者存在修改平台默认密码等校验规则等需求;本文将讲解不改变平台代码的情况下,如何实现这些功能需求。 首次登录修改密码 方案概述 自定义User增加是否是第一次登录的属性,登录后执行一个扩展点。 判断是否是一次登录,如果是则返回对应的状态码,前端根据状态码重定向到修改密码的页面。修改完成则充值第一次登录的标识。 PS:首次登录的标识平台前端已默认实现 扩展PamirsUser(例如:DemoUser) /** * @author wangxian */ @Model.model(DemoUser.MODEL_MODEL) @Model(displayName = "用户", labelFields = {"nickname"}) @Model.Advanced(index = {"companyId"}) public class DemoUser extends PamirsUser { public static final String MODEL_MODEL = "demo.DemoUser"; @Field.Integer @Field.Advanced(columnDefinition = "bigint DEFAULT '0'") @Field(displayName = "公司ID", invisible = true) private Long companyId; /** * 默认true->1 */ @Field.Boolean @Field.Advanced(columnDefinition = "tinyint(1) DEFAULT '1'") @Field(displayName = "是否首次登录") private Boolean firstLogin; } 定义扩展点接口(实际项目按需要增加和删减接口的定义) import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient; @Ext(PamirsUserTransient.class) public interface PamirsUserTransientExtPoint { @ExtPoint PamirsUserTransient loginAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient loginCustomAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient firstResetPasswordAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient firstResetPasswordBefore(PamirsUserTransient user); @ExtPoint PamirsUserTransient modifyCurrentUserPasswordAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient modifyCurrentUserPasswordBefore(PamirsUserTransient user); } 编写扩展点实现(例如:DemoUserLoginExtPoint) @Order(0) @Component @Ext(PamirsUserTransient.class) @Slf4j public class DemoUserLoginExtPoint implements PamirsUserTransientExtPoint { @Override @ExtPoint.Implement public PamirsUserTransient loginAfter(PamirsUserTransient user) { return checkFirstLogin(user); } private PamirsUserTransient checkFirstLogin(PamirsUserTransient user) { //首次登录需要修改密码 Long userId = PamirsSession.getUserId(); if (userId == null) { return user; } DemoUser companyUser = new DemoUser().queryById(userId); // 判断用户是否是第一次登录,如果是第一次登录,需要返回错误码,页面重新向登录 Boolean isFirst = companyUser.getFirstLogin(); if (isFirst) { //如果是第一次登录,返回一个标识给前端。 // 首次登录的标识平台已默认实现 user.setBroken(Boolean.TRUE); user.setErrorCode(UserExpEnumerate.USER_FIRST_LOGIN_ERROR.code()); return user; } return user; } @Override public PamirsUserTransient loginCustomAfter(PamirsUserTransient user) { return checkFirstLogin(user); } @Override…

    2024年5月25日
    5.8K00
  • 技术精要:数据导出与固化实用指南

    数据被认为是企业发展和决策的重要资产。随着业务的不断发展和数据量的不断增加,企业通常需要将数据从不同的源头导出,并将其固化到产品中,以便进行进一步的分析、处理和利用。数据导出与固化的过程涉及到数据的提取、清洗、整合和存储,是确保数据长期有效性和可用性的关键步骤。 了解数据导出与固化的流程和方法对于企业具有重要意义。通过有效的数据导出和固化,企业可以更好地管理和利用数据资源,提升决策的准确性和效率,实现业务的持续发展和创新。本次讨论将重点探讨数据导出与固化的流程和关键步骤,帮助参与者深入了解如何将数据从导出到产品中,为企业数据管理和应用提供有力支持。 1. 数据导出与固化:将数据从导出到产品中的流程 1.1. pom依赖: <dependency> <groupId>pro.shushi.pamirs.metadata.manager</groupId> <artifactId>pamirs-metadata-manager</artifactId> </dependency> 1.2 将第⼆步下载后的⽂件放⼊项⽬中(注意⽂件放置的位置)。放置⼯程的resources 下⾯。例如: 1.3 项⽬启动过程中,将⽂件中的数据导⼊(通常放在core模型的init包下 ⾯)。⽰例代码: package pro.shushi.pamirs.sys.setting.enmu; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCom mand; import pro.shushi.pamirs.boot.common.api.init.LifecycleCompleted AllInit; import pro.shushi.pamirs.boot.common.extend.MetaDataEditor; import pro.shushi.pamirs.core.common.InitializationUtil; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.meta.Meta; import pro.shushi.pamirs.meta.domain.module.ModuleDefinition; import pro.shushi.pamirs.metadata.manager.core.helper.DesignerIn stallHelper; import pro.shushi.pamirs.metadata.manager.core.helper.WidgetInst allHelper; import java.util.List; import java.util.Map; @Slf4j @Component public class DemoAppMetaInstall implements MetaDataEditor, LifecycleCompletedAllInit { @Autowired private ApplicationContext applicationContext; @Override public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) { if (!doImport()) { return; } log.info("[设计器业务元数据导⼊]"); InitializationUtil bizInitializationUtil = InitializationUtil.get(metaMap, DemoModule.MODULE_MODULE/ ***改成⾃⼰的Module*/, DemoModule.MODULE_NAME/***改成⾃⼰的 Module*/); DesignerInstallHelper.mateInitialization(bizInitializatio nUtil, "install/meta.json"); log.info("[⾃定义组件元数据导⼊]"); // 写法1: 将组件元数据导⼊到⻚⾯设计器. 只有在安装设计器的 服务中执⾏才有效果 WidgetInstallHelper.mateInitialization(metaMap, "install/widget.json"); // 写法2: 与写法1相同效果 InitializationUtil uiInitializationUtil = InitializationUtil.get(metaMap, "ui_designer", "uiDesigner"); if (uiInitializationUtil != null) { DesignerInstallHelper.mateInitialization(uiInitialization Util, "install/widget.json"); } // 写法3: 业务⼯程和设计器分布式部署,且希望通过业务⼯程导⼊ ⾃定义组件元数据. 业务模块需要依赖⻚⾯设计器模块,然后指定业务模块导 ⼊ DesignerInstallHelper.mateInitialization(bizInitializatio nUtil, "install/widget.json"); } @Override public void process(AppLifecycleCommand command, Map<String, ModuleDefinition> runModuleMap) { if (!doImport()) { return; } log.info("[设计器业务数据导⼊]"); // ⽀持远程调⽤,但是执⾏的⽣命周期必须是 LifecycleCompletedAllInit或之后. 本地如果安装了设计器,则没有要 求 DesignerInstallHelper.bizInitialization("install/ meta.json"); log.info("[⾃定义组件业务数据导⼊]"); // 当开发环境和导⼊环境的⽂件服务不互通时, 可通过指定js和 css的⽂件压缩包,⾃动上传到导⼊环境,并替换导⼊组件数据中的⽂件url // WidgetInstallHelper.bizInitialization("install/ widget.json", "install/widget.zip"); WidgetInstallHelper.bizInitialization("install/ widget.json"); return; } private boolean doImport() { // ⾃定义导⼊判断. 避免⽤于设计的开发环境执⾏导⼊逻辑 String[] envs = applicationContext.getEnvironment().getActiveProfiles(); List<String> envList = Lists.newArrayList(envs); return…

    2024年2月27日
    2.6K00
  • Oinone项目引入Nacos作为配置中心

    Oinone项目引入nacos作为配置中心 Oinone项目配置默认读取的项目中yml文件(application-xxx.yml), 实际项目中有可能要求项目的配置放到Nacos配置中心中; Oinone默认引入的nacos-client-1.4.1,低于1.4.1的版本不支持认证配置;1.4.1的客户端版本支持Nacos服务端1.x的和2.x的版本; 一、项目中增加依赖 项目主pom引入依赖(最新版平台已默认引入), Nacos版本要求1.4.1以上,低版本不支持认证配置 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2021.1</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.4.1</version> </dependency> 项目的boot工程的pom引入依赖(最新版平台已默认引入) <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </dependency> 二、项目工程bootstrap配置 bootstrap.yml文件的配置修改为: spring: application: name: hr-simple # 替换为实际服务名 profiles: active: wx # 指定 profile cloud: config: enabled: false nacos: discovery: enabled: false config: server-addr: 127.0.0.1:8848 enabled: true # namespace: # 如果使用 public 命名空间,建议省略此行 # namespace: your-custom-namespace-id # 如果使用自定义命名空间,填写其 ID group: DEFAULT_GROUP # prefix: # 通常省略,使用默认的 spring.application.name file-extension: yaml # 推荐使用 yaml,而不是 yml (虽然通常兼容) timeout: 5000 #【可选】修改为和nacos一致即可(如果服务端未开启可以不用配置) # username: wangxian # password: wangxian 三、Naocs服务端配置 在Nacos服务端的对应的namespace(5a8b3710-a9a2-4f7c-932f-50f326cb1ccf)下增加配置,把原本配置在代码中的(application-xxx.yml)配置到Nacos中

    2024年2月28日
    2.0K00
  • 自定义数据权限拦截处理

    业务场景 公司给员工对哪些模块有访问权限,这个时候就需要在员工访问模块表的时候做数据过滤, 解决方案 我们可以通过平台提供的数据过滤占位符解决这个问题,新建一条数据行权限,过滤语句条件是占位符,再编写占位符的解析逻辑 1.初始化权限基础数据 package pro.shushi.pamirs.demo.core.init; import com.google.common.collect.Lists; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.constants.AuthConstants; import pro.shushi.pamirs.auth.api.enmu.AuthGroupTypeEnum; import pro.shushi.pamirs.auth.api.enmu.PermissionDataSourceEnum; import pro.shushi.pamirs.auth.api.enmu.PermissionTypeEnum; import pro.shushi.pamirs.auth.api.model.AuthGroup; import pro.shushi.pamirs.auth.api.model.AuthRole; import pro.shushi.pamirs.auth.api.model.ResourcePermission; import pro.shushi.pamirs.boot.base.model.UeModule; 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.demo.core.placeholder.EmployeeModulePlaceholder; import pro.shushi.pamirs.framework.common.utils.ObjectUtils; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.domain.module.ModuleDefinition; import java.util.Collections; import java.util.List; @Slf4j @Component @Order(0) public class DemoModuleBizInit implements InstallDataInit, UpgradeDataInit { @Override public List<String> modules() { return Collections.singletonList(DemoModule.MODULE_MODULE); } @Override public int priority() { return 0; } @Override public boolean init(AppLifecycleCommand command, String version) { this.initAuth(); return true; } @Override public boolean upgrade(AppLifecycleCommand command, String version, String existVersion) { this.initAuth(); return true; } private void initAuth() { AuthGroup authGroup = new AuthGroup(); authGroup.setName("测试权限组") .setDisplayName("测试权限组") .setType(AuthGroupTypeEnum.RUNTIME) .setActive(true); authGroup.createOrUpdate(); AuthRole authRole = new AuthRole(); authRole.setCode("TEST_ROLE_1") .setName("测试角色") .setRoleTypeCode(AuthConstants.ROLE_SYSTEM_TYPE_CODE) .setPermissionDataSource(PermissionDataSourceEnum.CUSTOM) .setActive(true); authRole.createOrUpdate(); authRole.setGroups(Lists.newArrayList(authGroup)); authRole.fieldSave(AuthRole::getGroups); ResourcePermission authPermission = new ResourcePermission(); authPermission.setName("测试模块权限过滤") .setDomainExp(EmployeeModulePlaceholder.PLACEHOLDER) .setModel(ModuleDefinition.MODEL_MODEL) .setPermRead(true) .setPermRun(true) .setPermissionType(PermissionTypeEnum.ROW) .setPermissionDataSource(PermissionDataSourceEnum.CUSTOM) .setCanShow(true) .setActive(true); ResourcePermission authPermission2 = ObjectUtils.clone(authPermission); authPermission2.setName("测试ue模块权限过滤").setModel(UeModule.MODEL_MODEL); authGroup.setPermissions(Lists.newArrayList(authPermission, authPermission2)); authGroup.fieldSave(AuthGroup::getPermissions); } } 这里演示的module表比较特殊,需要同时设置ModuleDefinition和UeModule这2个模型做数据过滤 2.编写占位符拦截替换逻辑 package pro.shushi.pamirs.demo.core.placeholder; import org.springframework.stereotype.Component; import pro.shushi.pamirs.user.api.AbstractPlaceHolderParser; @Component public class EmployeeModulePlaceholder extends AbstractPlaceHolderParser { public static final String PLACEHOLDER = "${employeeModulePlaceholder}"; protected String value() { // TODO…

    2023年11月24日
    1.3K00

Leave a Reply

Please Login to Comment