3.2 Oinone以模块为组织

模块(module):是按业务领域划分和管理的最小单元,是一组功能、界面的集合。

带大家快速认识下如何构建一个oinone的模块并启动它。我会从以下几个维度去介绍模块的构建与启动方式、模块详解。让大家直观且全方位地了解oinone的模块所包含的内容

  1. 构建第一个Module

  2. 启动前端工程

  3. 应用中心

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

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

Like (1)
史, 昂's avatar史, 昂数式管理员
Previous 2024年5月23日 am9:55
Next 2024年5月23日 am9:57

相关推荐

  • 2.4.1 Oinone独特性之单体与分布式的灵活切换

    企业数字化转型需要处理分布式带来的复杂性和成本问题。尽管这些问题令人望而却步,但分布式架构对于大部分企业仍然是必须的选择。如果一个低代码平台缺乏分布式能力,那么它的性能就无法满足客户的要求。相比之下,Oinone平台通过对部署的创新(如图2-6所示),成功实现了分布式架构的支持,而且能够按照客户的业务发展需求,灵活选择不同的部署模式,同时节约企业成本,提升创新效率。这一创新是Oinone平台与其他低代码平台的重要区别,能够满足客户预期发展并兼顾成本效益。 图2-6 传统部署方式VS Oinone部署方式 实现原理 要实现灵活部署的特性,必须满足两个基本要求: 开发过程中不需要过多关注分布式技术,就像开发单体应用一样简单。代码在运行时应该能够根据模块是否在运行容器中,来决定路由走本地还是远程。这样可以大大减少研发人员的工作量和技术复杂度。 研发与部署要分离,即"开发单体应用一样开发分布式应用,而部署形式由后期决定"。为此,我们的工程结构支持多种启动模式,并逐一介绍了针对不同场景的工程结构类型(如下图2-7所示)。这样可以让客户在后期根据业务发展情况和需求,选择最适合的部署模式,从而达到灵活部署的目的。 图2-7 Oinone工程结构梳理 在整个工程结构上,我们秉承了Spring Boot的规范,不会改变大家的工程习惯。而Oinone的部署能力则可以让我们更灵活地应对各种情况。现在,我们来逐一介绍几种常规的工程结构以及它们适用的场景: 单模块工程结构(常规操作) a. 这是非常标准的Spring Boot工程,适用于简单的应用场景开发以及入门学习。 多模块工程结构(常规操作) a. 这是非常标准的多Spring Boot工程,可以实现分布式独立启动,适用于常规的分布式应用场景开发。 多模块工程结构-独立boot工程模式 a. 这种工程结构在多模块工程的基础上,通过独立的boot工程来支撑多部署方式。适用于中大型分布式应用场景开发。 b. 然而,随着工程越来越多,我们也会面临一些问题: ⅰ研发:环境准备非常困难,每个模块都要单独启动,研发调试跟踪困难。 ⅱ部署:分布式的高可靠性保证需要每个模块至少有两个部署节点,但在模块较多的情况下,起步成本非常高。同时,企业初期业务不稳定且规模较小,使用多模块工程的第二种模式会增加问题排查难度和成本。 c. 此时,Oinone的多模块工程下的独立boot工程模式部署就可以发挥其灵活性,让研发和业务起步阶段可以选择all-in-one模式,等到业务发展到一定规模的时候,只需要把线上部署模式切换成模块独立部署,而研发还可以保留all-in-one模式的优势。 d. 值得注意的是,分分合合的部署模式在传统互联网架构和低代码或无代码平台上都是有代价的,但是Oinone却可以灵活适配,只需要在boot工程的yml文件中写入需要加载的模块就可以解决。此处我们仅介绍多模块加载配置,选择性忽略其他无关配置,具体配置(如下图2-8所示)。 pamirs: boot: init: true sync: true modules: – base – resource – sequence – user – auth – web tenants: – pamirs 图2-8 Oinone yml配置图大型多场景工程结构-独立boot工程模式: a. 在多模块工程结构基础上的加强版,增加CDM层设计,让不同场景即保持数据统一,又保持逻辑独立。这种工程结构特别适用于大型企业软件开发,其中涉及到多个场景的情况,例如B端和C端的应用,或者跨不同业务线的应用,能够保证数据的一致性,同时也能够保持逻辑独立,避免不同场景间的代码冲突。 b. 这种工程结构是我们Oinone支撑“企业级软件生态”的核心,我们可以把场景A当作我们官方应用,场景B当作其他第三方伙伴应用。在这个工程结构下,我们的客户可以定制化开发自己的应用,同时我们也可以通过这种模式来支持我们的伙伴们进行开发,实现多方共赢。 c. 基于独立boot工程模式,我们同样对应多种部署模式应对不同情况,并统一管理所有伙伴应用。这种工程结构的优点是扩展性好,可以支持不同规模的应用,并且可以根据需要进行快速扩展或缩小规模,具有很高的灵活性。 基于标准产品的二开工程结构,是指基于标准产品进行二次开发,满足客户特定需求的工程结构。这种模式下,Oinone提供标准产品,客户可以根据自己的需求进行二次开发,实现定制化需求,同时可以利用我们的模块化开发特性,将每一个需求作为一个模块进行开发和管理。这种工程结构的优点是能够快速满足客户特定需求,同时也具有很好的可维护性和可扩展性,因为每个需求都是一个独立的模块,可以方便地进行维护和扩展。在下一篇“Oinone独特性之每一个需求都是一个模块”文章中有详细介绍。

    2024年5月23日
    1.9K00
  • 4.2.1 组件之生命周期

    组件生命周期的意义所在:比如动态创建了「视图、字段」,等它们初始化完成或者发生了修改后要执行业务逻辑,这个时候只能去自定义当前字段或者视图,体验极差,平台应该提供一些列的生命周期,允许其他人调用生命周期的api去执行对应的逻辑。 一、实现原理 图4-2-1-1 实现原理 当用户通过内部API去监听某个生命周期的时候,内部会动态的去创建该生命周期,每个生命周期都有「唯一标识」,内部会根据「唯一标识」去创建对应的「Effect」,Effect会根据生命周期的「唯一标识」实例化一个「lifeCycle」,「lifeCycle」创建完成后,会被存放到「Heart」中,「Heart」是整个生命周期的心脏,当心脏每次跳动的时候(生命周期被监听触发)都会触发对应的生命周期 二、生命周期API API 描述 返回值 View LifeCycle onViewBeforeCreated 视图创建前 ViewWidget onViewCreated 视图创建后 ViewWidget onViewBeforeMount 视图挂载前 ViewWidget onViewMounted 视图挂载后 ViewWidget onViewBeforeUpdate 视图数据发生修改前 ViewWidget onViewUpdated 视图数据修改后 ViewWidget onViewBeforeUnmount 视图销毁前 ViewWidget onViewUnmounted 视图销毁 ViewWidget onViewSubmit 提交数据 ViewWidget onViewSubmitStart 数据开始提交 ViewWidget onViewSubmitSuccess 数据提交成功 ViewWidget onViewSubmitFailed 数据提交失败 ViewWidget onViewSubmitEnd 数据提交结束 ViewWidget onViewValidateStart 视图字段校验 ViewWidget onViewValidateSuccess 校验成功 ViewWidget onViewValidateFailed 校验失败 ViewWidget onViewValidateEnd 校验结束 ViewWidget Field LifeCycle onFieldBeforeCreated 字段创建前 FieldWidget onFieldCreated 字段创建后 FieldWidget onFieldBeforeMount 字段挂载前 FieldWidget onFieldMounted 字段挂载后 FieldWidget onFieldBeforeUpdate 字段数据发生修改前 FieldWidget onFieldUpdated 字段数据修改后 FieldWidget onFieldBeforeUnmount 字段销毁前 FieldWidget onFieldUnmounted 字段销毁 FieldWidget onFieldFocus 字段聚焦 FieldWidget onFieldChange 字段的值发生了变化 FieldWidget onFieldBlur 字段失焦 FieldWidget onFieldValidateStart 字段开始校验 FieldWidget onFieldValidateSuccess 校验成功 FieldWidget onFieldValidateFailed 校验失败 FieldWidget onFieldValidateEnd 校验结束 FieldWidget 表4-2-1-1 生命周期API 上面列出的分别是「视图、字段」的生命周期,目前Action的生命周期还没有,后续再补充。 三、第一个View组件生命周期的监听(举例) Step1 新建registryLifeCycle.ts 新建registryLifeCycle.ts,监听宠物达人的列表页。’宠物达人table_demo_core’为视图名,您需要找后端配合 import { onViewCreated } from '@kunlun/dependencies' function registryLifeCycle(){ onViewCreated('宠物达人table_demo_core', (viewWidget) => { console.log('宠物达人table_demo_core'); console.log(viewWidget); }); } export {registryLifeCycle} 图4-2-1-2 新建registryLifeCycle.ts Step2 修改main.ts 全局注册lifeCycle import { registryLifeCycle } from './registryLifeCycle'; registryLifeCycle(); 图4-2-1-3 修改main.ts Step3 看效果 图4-2-1-4 示例效果 四、第一个Filed组件生命周期的监听(举例) Step1 修改registryLifeCycle.ts 通过onFieldValueChange增加宠物达人搜索视图的name(达人)字段的值变化进行监听。 宠物达人search:name 代表 视图名:字段名 import { onViewCreated , onFieldValueChange} from '@kunlun/dependencies' function registryLifeCycle(){ onViewCreated('宠物达人table_demo_core', (viewWidget) => { console.log('宠物达人table_demo_core'); console.log(viewWidget); }); onFieldValueChange('宠物达人search:name', (filedWidget) => { console.log('宠物达人search:name');…

    Oinone 7天入门到精通 2024年5月23日
    1.1K00
  • 3.4.3.3 SPI机制-扩展点

    扩展点结合拦截器的设计,oinone可以点、线、面一体化管理Function 扩展点用于扩展函数逻辑。扩展点类似于SPI机制(Service Provider Interface),是一种服务发现机制。这一机制为函数逻辑的扩展提供了可能。 一、构建第一个扩展点 自定义扩展点(举例) 在我们日常开发中,随着对业务理解的深入,往往还在一些逻辑中会预留扩展点,以便日后应对不同需求时可以灵活替换某一小块逻辑。 在3.3.4【模型的继承】一文中的PetCatItemQueryService,是独立新增函数只作公共逻辑单元。现在我们给它的实现类增加一个扩展点。在PetCatItemQueryServiceImpl的queryPage方法中原本会先查询PetCatType列表,我们这里假设这个逻辑随着业务发展未来会发生变化,我们可以预先预留【查询萌猫类型扩展点】 Step1 新增扩展点定义PetCatItemQueryCatTypeExtpoint 扩展点命名空间:在接口上用@Ext声明扩展点命名空间。会优先在本类查找@Ext,若为空则往接口向上做遍历查找,返回第一个查找到的@Ext.value注解值,使用该值再获取函数的命名空间;如果未找到,则返回扩展点全限定类名。所以我们这里扩展点命名空间为:pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint 扩展点技术名称:先取@ExtPoint.name,若为空则取扩展点接口方法名。所以我们这里技术名为queryCatType package pro.shushi.pamirs.demo.api.extpoint; import pro.shushi.pamirs.demo.api.model.PetCatType; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import java.util.List; @Ext public interface PetCatItemQueryCatTypeExtpoint { @ExtPoint(displayName = "查询萌猫类型扩展点") List<PetCatType> queryCatType(); } 图3-4-3-11 新增扩展点定义PetCatItemQueryCatTypeExtpoint Step2 修改PetCatItemQueryServiceImpl(用Ext.run模式调用) 修改queryPage,增加扩展点的使用代码。扩展点的使用有两种方式 方法一,使用命名空间和扩展点名称调用Ext.run(namespace, fun, 参数); 方法二,使用函数式接口调用Ext.run(函数式接口, 参数); 我们这里用了第二种方式 用PetCatItemQueryCatTypeExtpoint的全限定类名作为扩展点的命名空间(namespace) 用queryCatType的方法名作为扩展点的技术名称(name) 根据namespace+name去找到匹配扩展点实现,并根据规则是否匹配,以及优先级唯一确定一个扩展点实现去执行逻辑 package pro.shushi.pamirs.demo.core.service; ……省略依赖包 @Model.model(PetCatItem.MODEL_MODEL) @Component public class PetCatItemAction extends DataStatusBehavior<PetCatItem> { @Override protected PetCatItem fetchData(PetCatItem data) { return data.queryById(); } @Action(displayName = "启用") public PetCatItem dataStatusEnable(PetCatItem data){ data = super.dataStatusEnable(data); data.updateById(); return data; } @Function.Advanced(displayName = "查询模型数据的默认过滤条件", type = FunctionTypeEnum.QUERY, managed = true) @Function(openLevel = {LOCAL}) public String queryFilters() { StringBuilder sqlWhereCondition = new StringBuilder(); // List<PetCatType> typeList = new PetCatType().queryList(); List<PetCatType> typeList = Ext.run(PetCatItemQueryCatTypeExtpoint::queryCatType, new Object[]{}); if(!CollectionUtils.isEmpty(typeList)){ // sqlWhereCondition.append("type_id"); sqlWhereCondition.append(PStringUtils.fieldName2Column(LambdaUtil.fetchFieldName(PetCatItem::getTypeId))); sqlWhereCondition.append(StringUtils.SPACE).append(SqlConstants.IN).append(CharacterConstants.LEFT_BRACKET); for(PetCatType petCatType: typeList){ sqlWhereCondition.append(petCatType.getId()).append(CharacterConstants.SEPARATOR_COMMA); } sqlWhereCondition.deleteCharAt(sqlWhereCondition.lastIndexOf(CharacterConstants.SEPARATOR_COMMA)); sqlWhereCondition.append(StringUtils.SPACE).append(CharacterConstants.RIGHT_BRACKET); } return sqlWhereCondition.toString(); } ……省略其他函数 } 图3-4-3-12 修改PetCatItemQueryServiceImpl Step3 新增扩展点实现PetCatItemQueryCatTypeExtpointOne 扩展点命名空间要与扩展点定义一致,用@Ext(PetCatItemQueryCatTypeExtpoint.class) @ExtPoint.Implement声明这是在@Ext声明的命名空间下,且技术名为queryCatType的扩展点实现 package pro.shushi.pamirs.demo.core.extpoint; import pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint; import pro.shushi.pamirs.demo.api.model.PetCatType; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.meta.api.session.PamirsSession; import java.util.List; @Ext(PetCatItemQueryCatTypeExtpoint.class) public class PetCatItemQueryCatTypeExtpointOne implements PetCatItemQueryCatTypeExtpoint { @Override @ExtPoint.Implement(displayName = "查询萌猫类型扩展点的默认实现") public List<PetCatType> queryCatType() { PamirsSession.getMessageHub().info("走的是第一个扩展点"); List<PetCatType> typeList = new PetCatType().queryList(); return typeList; } } 图3-4-3-13 新增扩展点实现PetCatItemQueryCatTypeExtpointOne Step4…

    2024年5月23日
    1.6K00
  • 4.1.9 函数之元位指令

    元位指令系统是通过给请求上下文的指令位字段作按位与标记来对函数处理下发对应指令的系统。 一、元位指令介绍 元位指令系统是通过给请求上下文的指令位字段作按位与标记来对函数处理下发对应指令的系统。 元位指令系统分为请求上下文指令和数据指令两种。 数据指令 数据指令基本都是系统内核指令。业务开发时用不到这里就不介绍了。前20位都是系统内核预留 请求上下文指令 请求上下文指令:使用session上下文中非持久化META_BIT属性设置指令。 位 指令 指令名 前端默认值 后端默认值 描述 20 builtAction 内建动作 否 否 是否是平台内置定义的服务器动作对应操作:PamirsSession.directive().disableBuiltAction(); PamirsSession.directive().enableBuiltAction(); 21 unlock 失效乐观锁 否 否 系统对带有乐观锁模型默认使用乐观锁对应操作:PamirsSession.directive().enableOptimisticLocker(); PamirsSession.directive().disableOptimisticLocker(); 22 check 数据校验 是 否 系统后端操作默认不进行数据校验,标记后生效数据校验对应操作:PamirsSession.directive().enableCheck(); PamirsSession.directive().disableCheck(); 23 defaultValue 默认值计算 是 否 是否自动填充默认值对应操作:PamirsSession.directive().enableDefaultValue(); PamirsSession.directive().disableDefaultValue(); 24 extPoint 执行扩展点 是 否 前端请求默认执行扩展点,可以标记忽略扩展点。后端编程式调用数据管理器默认不执行扩展点对应操作:PamirsSession.directive().enableExtPoint(); PamirsSession.directive().disableExtPoint(); 25 hook 拦截 是 否 是否进行函数调用拦截对应操作:PamirsSession.directive().enableHook(); PamirsSession.directive().disableHook(); 26 authenticate 鉴权 是 否 系统默认进行权限校验与过滤,标记后使用权限校验对应操作:PamirsSession.directive().sudo(); PamirsSession.directive().disableSudo(); 27 ormColumn ORM字段别名 否 否 系统指令,请勿设置 28 usePkStrategy 使用PK策略 是 否 使用PK是否空作为采用新增还是更新的持久化策略对应操作:PamirsSession.directive().enableUsePkStrategy(); PamirsSession.directive().disableUsePkStrategy(); 29 fromClient 是否客户端调用 是 否 是否客户端(前端)调用对应操作:PamirsSession.directive().enableFromClient(); PamirsSession.directive().disableFromClient(); 30 sync 同步执行函数 否 否 异步执行函数强制使用同步方式执行(仅对Spring Bean有效) 31 ignoreFunManagement 忽略函数管理 否 否 忽略函数管理器处理,防止Spring调用重复拦截对应操作:PamirsSession.directive().enableIgnoreFunManagement(); PamirsSession.directive().disableIgnoreFunManagement(); 表4-1-9-1 请求上下文指令 二、使用指令 普通模式 PamirsSession.directive().disableOptimisticLocker(); try{ 更新逻辑 } finally { PamirsSession.directive().enableOptimisticLocker(); } 图4-1-9-1 普通模式代码示意 批量设置模式 Models.directive().run(() -> {此处添加逻辑}, SystemDirectiveEnum.AUTHENTICATE) 图4-1-9-2 批量设置模式代码示意 三、使用举例 我们在4.1.5【模型之持久层配置】一文中提到过失效乐观锁,我们在这里就尝试下吧。 Step1 修改PetItemInventroyAction 手动失效乐观锁 package pro.shushi.pamirs.demo.core.action; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.model.PetItemInventroy; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.constant.FunctionConstants; import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum; import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum; import java.util.ArrayList; import java.util.List; @Model.model(PetItemInventroy.MODEL_MODEL) @Component public class PetItemInventroyAction { @Function.Advanced(type= FunctionTypeEnum.UPDATE) @Function.fun(FunctionConstants.update) @Function(openLevel = {FunctionOpenEnum.API}) public PetItemInventroy update(PetItemInventroy data){ List<PetItemInventroy> inventroys = new ArrayList<>(); inventroys.add(data); PamirsSession.directive().disableOptimisticLocker(); try{ //批量更新会,自动抛错 int i = data.updateBatch(inventroys); //单记录更新,不自动抛售需要自行判断 // int i = data.updateById();…

    2024年5月23日
    1.2K00
  • 3.5.7.2 自定义母版

    母版是什么 在业务系统中,母版是一种全局通用的组件,用于包裹每个页面的元素。这个概念类似于 PPT 中的母版,它定义了页面的整体结构、样式和布局,使得系统具有一致的外观和风格。 特点: 全局通用:母版是全局性的,适用于系统中的每个页面,确保一致性的用户体验。 包裹元素:母版包裹每个页面的元素,定义了整体的结构和布局。 外观一致性:通过统一的母版设计,系统达到外观和风格的一致性。 与 PPT 母版的类比: 在母版的概念上,与 PPT 中的母版类似,都是用于定义整体结构和样式,确保每个页面都具有一致的外观。 默认母版范围: 图3-5-7-20 默认母版范围 作用场景 在系统中,我们提供了多个纬度的母版,包括全局、应用和页面纬度。这样的设计允许根据不同的业务场景选择合适的母版纬度,以满足不同层次的定制需求。 母版纬度: 全局母版: 覆盖系统中所有页面,确保全局的一致性和统一的用户体验。 定义全局性的结构、样式和布局。 应用母版: 适用于特定应用或模块,定制化程度介于全局和页面之间。 允许在不同应用间实现一定的差异化。 页面母版: 面向具体页面,提供最大的定制化空间。 允许在不同页面中定义不同的结构和样式。 选择纬度: 业务场景决定:根据具体的业务场景和需求选择合适的母版纬度。 定制化需求:如果需要全局一致性,选择全局母版;如果在应用层面有特定需求,选择应用母版;如果需要最大的灵活性和定制化,选择页面母版。 参数说明: 在系统中,使用 registerMask 注册母版时,需要提供两个参数。第一个参数是母版的 XML,第二个参数用于控制母版的注册行为,包括 module、moduleName、model 和 actionName。 母版 XML(第一个参数): 提供母版的 XML 描述,定义了母版的结构和样式。 控制参数(第二个参数): module:指定模块的名称。 moduleName:指定模块的显示名称。 model:指定母版所属的模型。 actionName:指定母版的操作名称,可以是字符串或字符串数组。 全局母版 可以使用 registerMask 注册母版。当第二个参数为 {} 时,即代表注册全局母版。 示例工程目录 以下是需关注的工程目录示例,并确保导入导出依赖正确: 图3-5-7-21 全局纬度注册母版工程目录示例 示例代码 以下是一个示例代码: import { registerMask } from '@kunlun/dependencies'; /** * mask: 在做系统时,我们通常会把外层的布局(菜单、顶部导航)等抽出公共组件,公共组件会抽离全局通用。类似ppt母版的概念即为Mask * registerMask 第二个入参为{}即注册了全局得母版 * * 和默认母版相比移除右上角消息、语言切换、用户 * `<block> * <widget widget="notification" /> * <widget widget="divider" /> * <widget widget="language" /> * <widget widget="divider" /> * <widget widget="user" /> * </block>` */ const registerGlobalMask = () => { registerMask(` <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask>`, {}); } registerGlobalMask(); 图3-5-7-22 全局纬度注册母版代码示例 复制这段代码执行下registerGlobalMask()试试看吧 效果 右上角消息、语言切换、用户被移除。 图3-5-7-23 全局纬度注册母版效果示例 应用母版 在系统中,可以使用 registerMask 注册母版。当第二个参数为 {moduleName: ‘resource’} 时,即注册了 resource(资源)应用的母版。 示例工程目录 以下是需关注的工程目录示例,更新导出./moduleMask: 图3-5-7-24 应用纬度注册母版工程目录示例 示例代码 以下是一个示例代码: /** * registerMask 第二个入参为{moduleName: 'resource'}即注册了resource(资源)应用的母版 * * resource来源: 通常是浏览器url上的module * * 和默认模版相比拿掉了菜单 * `<sidebar> * <widget widget="nav-menu" height="100%"…

    2024年5月23日
    1.6K00

Leave a Reply

Please Login to Comment