3.3.2 模型的类型

本文会介绍不同类型模型以及其简单的应用场景,方便大家理解不同类型模型的用途

模型分为元模型和业务模型。元数据是指描述应用程序运行所必需的数据、规则和逻辑的数据集;元模型是指用于描述内核元数据的一套模式集合;业务模型是指用于描述业务应用元数据的一套模式集合。

元模型分为模块域、模型域和函数域三个域。域的划分规则是根据元模型定义数据关联关系的离散性来判断,离散程度越小越聚集到一个域。在4.1.4【模块元数据详解】一文中介绍的ModuleDefinition就是元模型。而我们在开发中涉及的就是业务模型

一、模型类型

  • 抽象模型:往往是提供公共能力和字段的模型,它本身不会直接用于构建协议和基础设施(如表结构等)。

  • 传输模型:用于表现层和应用层之间的数据交互,本身不会存储,没有默认的数据管理器,只有数据构造器。

  • 存储模型:存储模型用于定义数据表结构和数据的增删改查(数据管理器)功能,是直接与连接器进行交互的数据容器。

  • 代理模型:用于代理存储模型的数据管理器能力的同时,扩展出非存储数据信息的交互功能的模型。

二、模型定义种类

模型定义就是模型描述,不同定义类型,代表计算描述模型的元数据的规则不同

  • 静态模型定义:模型元数据不持久化、不进行模型定义的计算(默认值、主键、继承、关联关系)

  • 静态计算模型定义:模型元数据不持久化但初始化时进行模型定义计算获得最终的模型定义

  • 动态模型定义:模型元数据持久化且初始化时进行模型定义计算获得最终的模型定义

静态模型定义需要使用@Model.Static进行注解;静态计算模型定义使用@Model.Static(compute=true)进行注解;动态模型定义不注解@Model.Static注解。

三、安装与更新

使用@Model.model来配置模型的不可变更编码。模型一旦安装,无法在对该模型编码值进行修改,之后的模型配置更新会依据该编码进行查找并更新;如果仍然修改该注解的配置值,则系统会将该模型识别为新模型,存储模型会创建新的数据库表,而原表将会rename为废弃表。

如果模型配置了@Base注解,表明在【oinone的设计器】中该模型配置不可变更;如果字段配置了@Base注解,表明在【oinone的设计器】中该字段配置不可变更。

四、基础配置

模型基类

所有的模型都需要继承以下模型中的一种,来表明模型的类型,同时继承以下模型的默认数据管理器(详见3.3.3模型的数据管理器一节)。

  • 继承BaseModel,构建存储模型,默认无id属性。

  • 继承BaseRelation,构建多对多关系模型,默认无id属性。

  • 继承TransientModel,构建临时模型(传输模型),临时模型没有数据管理器,也没有id属性。

  • 继承EnhanceModel,构建数据源为ElasticSearch的增强模型。

快捷继承

  • 继承IdModel,构建主键为id的模型。继承IdModel的模型会数据管理器会增加queryById方法(根据id查询单条记录)

  • 继承CodeModel,构建带有唯一编码code的主键为id的模型。可以使用@Model.Code注解配置编码生成规则。也可以直接覆盖CodeModel的generateCode方法或者自定义新增的前置扩展点自定义编码生成逻辑。继承CodeModel的模型会数据管理器会增加queryByCode方法(根据唯一编码查询单条记录)

  • 继承VersionModel,构建带有乐观锁,唯一编码code且主键为id的模型。

  • 继承IdRelation,构建主键为id的多对多关系模型。

模型继承关系图

3.3.2 模型的类型

图3-3-2-1 模型继承关系图

  • AbstractModel抽象基类是包含createDate创建时间、writeDate更新时间、createUid创建用户ID、writeUid更新用户ID、aggs聚合结果和activePks批量主键列表等基础字段的抽象模型。

  • TransientModel传输模型抽象基类是所有传输模型的基类,传输模型不存储,没有数据管理器。

  • TransientRelation传输关系模型是所有传输关系模型的基类,传输关系模型不存储,用于承载多对多关系,没有数据管理器。

  • BaseModel存储模型基类提供数据管理器功能,数据模型主键可以不是ID。

  • IdModel带id模型抽象基类,在BaseModel数据管理器基础之上提供根据ID查询、更新、删除数据的功能。

  • BaseRelation关系模型抽象基类用于承载多对多关系,是多对多关系的中间模型,数据模型主键可以不是ID。

  • IdRelation带id关系模型抽象基类,在BaseRelation数据管理器基础之上提供根据ID查询、更新、删除数据的功能。

  • CodeModel带code模型抽象基类,提供按配置生成业务唯一编码功能,根据code查询、更新、删除数据的功能。

  • EnhanceModel增强模型,提供全文检索能力。此模型会在4.1.25【框架之搜索引擎】一文中展开介绍。

五、抽象模型(举例)

抽象模型本身不会直接用于构建协议和基础设施(如表结构等),而是通过继承的机制供子模型复用其字段和函数。子模型可以是所有类型的模型。

比如demo模块要管理的一些公共模型字段,我们可以建一个AbstractDemoIdModel和AbstractDemoCodeModel,demo模块中的实体模型就可以继承它们。我们来为demo模块的模型统一增加一个数据状态这么一个字段,用做数据的生效与失效管理。

Step1 引入DataStatusEnum类

pamirs-demo-api的pom.xml包增加依赖,便于引入DataStatusEnum类,当然也可以自己建,这里只是oinone提供了统一的数据记录状态的枚举,以及相应的通用方法,这边就直接引入

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

图3-3-2-2 引入通用类库

Step2 修改DemoModule

DataStatusEnum枚举类本身也会做为数据字典,以元数据的方式被管理起来,当一个模块依赖另一个模块的元数据相关对象,则需要改模块的模块依赖定义。为DemoModule增加CommonModule的依赖注解

package pro.shushi.pamirs.demo.api;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute;
import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxHomepage;
import pro.shushi.pamirs.core.common.CommonModule;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.meta.annotation.Module;
import pro.shushi.pamirs.meta.base.PamirsModule;
import pro.shushi.pamirs.meta.common.constants.ModuleConstants;

@Component
@Module(
        name = DemoModule.MODULE_NAME,
        displayName = "oinoneDemo工程",
        version = "1.0.0",
        dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE}
)
@Module.module(DemoModule.MODULE_MODULE)
@Module.Advanced(selfBuilt = true, application = true)
@UxHomepage(@UxRoute(PetShop.MODEL_MODEL))
public class DemoModule implements PamirsModule {
    public static final String MODULE_MODULE = "demo_core";
    public static final String MODULE_NAME = "DemoCore";

    @Override
    public String[] packagePrefix() {
        return new String[]{ "pro.shushi.pamirs.demo"};
    }
}

图3-3-2-3 定义模块依赖

Step3 新建AbstractDemoCodeModel和AbstractDemoIdModel

并新增AbstractDemoIdModel和AbstractDemoCodeModel分别继承IdModel和CodeModel,实现IDataStatus接口不是必须的,刚好DataStatus有配套的通用逻辑,暂时也先加进去,具体使用会在本文的【代理模型】这段介绍

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

import pro.shushi.pamirs.core.common.behavior.IDataStatus;
import pro.shushi.pamirs.core.common.enmu.DataStatusEnum;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.sys.Base;
import pro.shushi.pamirs.meta.base.common.CodeModel;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;

@Base
@Model.model(AbstractDemoCodeModel.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.ABSTRACT)
@Model(displayName = "AbstractDemoCodeModel")
public abstract class AbstractDemoCodeModel extends CodeModel implements IDataStatus {

    public static final String MODEL_MODEL="demo.AbstractDemoCodeModel";

    @Base
    @Field.Enum
    @Field(displayName = "数据状态",defaultValue = "DISABLED",required = true,summary = "作为基类给每一个继承模型增加一个数据状态字段")
    private DataStatusEnum dataStatus;
}

图3-3-2-4 新建AbstractDemoCodeModel

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

import pro.shushi.pamirs.core.common.behavior.IDataStatus;
import pro.shushi.pamirs.core.common.enmu.DataStatusEnum;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.sys.Base;
import pro.shushi.pamirs.meta.base.IdModel;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;

@Base
@Model.model(AbstractDemoIdModel.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.ABSTRACT)
@Model(displayName = "AbstractDemoIdModel")
public abstract class AbstractDemoIdModel extends IdModel implements IDataStatus {

    public static final String MODEL_MODEL="demo.AbstractDemoIdModel";

    @Base
    @Field.Enum
    @Field(displayName = "数据状态",defaultValue = "DISABLED",required = true,summary = "作为基类给每一个继承模型增加一个数据状态字段")
    private DataStatusEnum dataStatus;
}

图3-3-2-5 新建AbstractDemoIdModel

Step4 修改PetShop的父类

修改PetShop父类从IdMode变更为AbstractDemoIdModel

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="宠物店铺")
public class PetShop extends AbstractDemoIdModel {
    public static final String MODEL_MODEL="demo.PetShop";

    @Field(displayName = "店铺名称",required = true)
    private String shopName;

    @Field(displayName = "开店时间",required = true)
    private Time openTime;

    @Field(displayName = "闭店时间",required = true)
    private Time closeTime;
}

图3-3-2-6 修改PetShop的父类

重启看效果

宠物商店的列表页面和修改页面都增加数据状态字段,如下图3-3-2-7及3-3-2-8所示:

3.3.2 模型的类型

图3-3-2-7 宠物商店列表页增加数据状态字段

3.3.2 模型的类型

图3-3-2-8 宠物商店修改页增加数据状态字段

六、存储模型

存储模型用于定义数据表结构和数据的增删改查(数据管理器)功能,是直接与连接器进行交互的数据容器。

PetShop就是一个存储模型,这里就不过多举例子介绍了

七、代理模型(举例)

代理模型是用于代理存储模型的数据管理器能力,同时又可以扩展出非存储数据信息的交互功能的模型。

如果我们PetShop模型要展示创建人昵称?那么就可以建一个PetShopProxy类来完成,代理模型增加的字段都是非存储字段,只用于交互包括展示或提交

Step1 引入PamirsUser类

pamirs-demo-api的pom.xml包增加依赖,便于引入PamirsUser类。

<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-user-api</artifactId>
</dependency>

图3-3-2-9 引入基础类库user

Step2 新建PetShopProxy模型

新建一个PetShopProxy类继承PetShop,并声明类型为PROXY。同时增加一个creater字段

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

import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;
import pro.shushi.pamirs.user.api.model.PamirsUser;

@Model.model(PetShopProxy.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY)
@Model(displayName = "宠物店铺代理模型",summary="宠物店铺代理模型")
public class PetShopProxy extends PetShop {

    public static final String MODEL_MODEL="demo.PetShopProxy";

    @Field.many2one
    @Field(displayName = "创建者",required = true)
    @Field.Relation(relationFields = {"createUid"},referenceFields = {"id"})
    private PamirsUser creater;
}

图3-3-2-10 新建PetShopProxy模型

Step3 修改DemoModule

PamirsUser模型隶属于UserModule。为DemoModule增加UserModule的依赖注解,同时修改DemoModule的homepage注解,默认进入PetShopProxy的管理页面

package pro.shushi.pamirs.demo.api;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute;
import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxHomepage;
import pro.shushi.pamirs.core.common.CommonModule;
import pro.shushi.pamirs.demo.api.proxy.PetShopProxy;
import pro.shushi.pamirs.meta.annotation.Module;
import pro.shushi.pamirs.meta.base.PamirsModule;
import pro.shushi.pamirs.meta.common.constants.ModuleConstants;
import pro.shushi.pamirs.user.api.UserModule;

@Component
@Module(
        name = DemoModule.MODULE_NAME,
        displayName = "oinoneDemo工程",
        version = "1.0.0",
        dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE, UserModule.MODULE_MODULE})
@Module.module(DemoModule.MODULE_MODULE)
@Module.Advanced(selfBuilt = true, application = true)
@UxHomepage(@UxRoute(PetShopProxy.MODEL_MODEL))
public class DemoModule implements PamirsModule {

    public static final String MODULE_MODULE = "demo_core";
    public static final String MODULE_NAME = "DemoCore";

    @Override
    public String[] packagePrefix() {
        return new String[]{ "pro.shushi.pamirs.demo"};
    }
} 

图3-3-2-11 定义模块依赖及修改首页入口

Step4 新增PetShopProxyAction类

为了展示效果覆盖PetShopProxy默认从PetShop继承的数据管理器方法,但对于Action和Function这些这里不展开介绍,具体会3.4【oinone以函数为内在】和3.5【oinone以交互为外在】两章中介绍。

  1. 新建一个PetShopProxyAction类,Model.model设置为PetShopProxy,也就是把所有aciton和function都挂在PetShopProxy这个模型载体上

  2. PetShopProxyAction继承DataStatusBehavior类配套dataStatus使用,定义一个【启用】serverAction,而启用逻辑则复用父类DataStatusBehavior的dataStatusEnable方法。

  3. 覆盖【queryPage】Function,该方法名为前后约定协议中列表查询的默认方法

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

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.behavior.impl.DataStatusBehavior;
import pro.shushi.pamirs.demo.api.proxy.PetShopProxy;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;

@Model.model(PetShopProxy.MODEL_MODEL)
@Component
public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> {

    @Override
    protected PetShopProxy fetchData(PetShopProxy data) {
        return data.queryById();
    }
    @Action(displayName = "启用")
    public PetShopProxy dataStatusEnable(PetShopProxy data){
        data = super.dataStatusEnable(data);
        data.updateById();
        return data;
    }

    @Function.Advanced(type= FunctionTypeEnum.QUERY)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.API})
    public Pagination<PetShopProxy> queryPage(Pagination<PetShopProxy> page, IWrapper<PetShopProxy> queryWrapper){
        Pagination<PetShopProxy> result = new PetShopProxy().queryPage(page,queryWrapper);
        new PetShopProxy().listFieldQuery(result.getContent(),PetShopProxy::getCreater);
        return result;
    }

}

图3-3-2-12 新增PetShopProxyAction类

Step5 重启看效果

宠物商店的列表页面和修改页面都增加数据状态字段,如下图3-3-24所示:

3.3.2 模型的类型

图3-3-2-13 宠物商店页面效果

多次点击启用会报对应的错误。当然如果场景不需要,则没有必要复用DataStatusBehavior逻辑,也就不需要实现IDataStatus接口了,启动按钮只在禁用状态展示在后续的教程中会学到

3.3.2 模型的类型

图3-3-2-14 启用操作效果

八、传输模型(举例)

由于传输模型没有默认的数据管理器,只有数据构造器,所以在不自定义动作的情况下,传输模型可以打开详情页、新增表单和修改表单和列表页,但是所有的动作全部为窗口动作。传输模型本身不会存储,如果是关联关系字段关联传输模型,可以将传输模型序列化存储在模型的关联关系字段上。因为没有数据管理器,所以传输模型的列表页没有分页能力。

场景举例:如果我们想批量修改PetShop的数据状态,那么我们需要在列表页选中数据记录点击【批量修改】跳转到一个批量修改页面,选择要修改的数据状态,并点确认提交。那么传输模型就可以承载批量修改页面、数据以及操作的载体模型。

Step1 新建PetShopBatchUpdate模型

新建一个PetShopBatchUpdate类继承TransientModel,同时增加petShopList和dataStatus字段用于接收页面传值

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

import pro.shushi.pamirs.core.common.enmu.DataStatusEnum;
import pro.shushi.pamirs.demo.api.proxy.PetShopProxy;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.base.TransientModel;

import java.util.List;

@Model.model(PetShopBatchUpdate.MODEL_MODEL)
@Model(displayName = "批量修改宠物商店数据状态",summary = "批量修改宠物商店数据状态")
public class PetShopBatchUpdate extends TransientModel {
    public static final String MODEL_MODEL="demo.PetShopBatchUpdate";

    @Field(displayName = "数据状态",required = true)
    private DataStatusEnum dataStatus;

    @Field(displayName = "宠物商店列表",required = true)
    @Field.many2many
    private List<PetShopProxy> petShopList;
}

图3-3-2-15 新建PetShopBatchUpdate模型

Step2 新增PetShopBatchUpdateAction类

传输模型没有默认的数据管理器,只有数据构造器(construct)。

  1. 新建一个PetShopBatchUpdateAction类,Model.model设置为PetShopBatchUpdate,也就是把所有Aciton和Function都挂在PetShopBatchUpdateAction这个模型载体上

  2. 覆盖数据构造器(construct),接收从宠物商店列表多选带过来的数据参数,非PetShopBatchUpdate本模型参数不能放第一个,用List petShopList来接收,进行数据组装逻辑处理,对应数据也是由PetShopBatchUpdate来承载返回给PetShopBatchUpdate的Form编辑页。

  3. 定义一个【确定】serverAction,绑定PetShopBatchUpdate的Form编辑页。点击确定则批量修改Form页面中的宠物商店列表为指定的数据状态

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

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.proxy.PetShopProxy;
import pro.shushi.pamirs.demo.api.tmodel.PetShopBatchUpdate;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ActionContextTypeEnum;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.meta.enmu.ViewTypeEnum;

import java.util.List;

@Model.model(PetShopBatchUpdate.MODEL_MODEL)
@Component
public class PetShopBatchUpdateAction {

    @Function(openLevel = FunctionOpenEnum.API)
    @Function.Advanced(type= FunctionTypeEnum.QUERY)
    public PetShopBatchUpdate construct(PetShopBatchUpdate petShopBatchUpdate, List<PetShopProxy> petShopList){
        PetShopBatchUpdate result = new PetShopBatchUpdate();
        //前端传递只有id,补全下数据
        petShopList = FetchUtil.fetchList(petShopList);
        //补全创建者字段
        new PetShopProxy().listFieldQuery(petShopList,PetShopProxy::getCreater);
        result.setPetShopList(petShopList);
        return result;
    }

    @Action(displayName = "确定",bindingType = ViewTypeEnum.FORM,contextType = ActionContextTypeEnum.SINGLE)
    public PetShopBatchUpdate conform(PetShopBatchUpdate data){
        List<PetShopProxy> proxyList = data.getPetShopList();
        for(PetShopProxy petShopProxy:proxyList){
            petShopProxy.setDataStatus(data.getDataStatus());
        }
        new PetShopProxy().updateBatch(proxyList);
        return data;
    }
}

图3-3-2-16 新建PetShopBatchUpdateAction类

Step3 注解式初始化ViewAction窗口动作

在PetShopProxyAction增加类注解,并注释掉DemoModuleMetaDataEditor的viewActionInit方法体代码。用UxRouteButton注解来申明一个ViewAction。更多Ux系列注解详见3.5.4【Ux注解详解】一文

  1. @Model.model(PetShopProxy.MODEL_MODEL),代表UxRouteButton申明viewAction所在模型

  2. @UxRoute.model代表viewAction的resModel,路由的目标模型

@Model.model(PetShopProxy.MODEL_MODEL)
@UxRouteButton(action = @UxAction(name = "demo_petShop_batch_update", label = "批量更新数据状态",contextType = ActionContextTypeEnum.SINGLE_AND_BATCH), value = @UxRoute(model = PetShopBatchUpdate.MODEL_MODEL, viewType = ViewTypeEnum.FORM,openType = ActionTargetEnum.DIALOG,viewName= ViewConstants.Name.formView))
@Component
public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> {}

图3-3-2-17 在PetShopProxyAction增加类注解

Step4 重启看效果

3.3.2 模型的类型

图3-3-2-18 批量操作效果示意

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

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

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

相关推荐

  • 2.4.2 Oinone独特性之每一个需求都可以是一个模块

    我们的Oinone平台采用模型驱动的方式,并符合面向对象设计原则,每个需求都可以是一个独立模块,可以独立安装、升级和卸载。这让系统真正像乐高积木一样搭建,具有高度的灵活性和可维护性。 与大部分低代码或无代码平台不同的是,它们的应用市场上的应用往往是模板式的,也就是说,这是一个拷贝,个性化只能在应用上直接修改,而且一旦修改就不能升级。这对于软件公司和客户来说都非常痛苦。客户无法享受到软件公司产品的升级功能,而软件公司在服务大量客户时,也会面临不同版本的维护问题,成本也非常高。而我们的Oinone平台完全避免了这些问题,让客户和软件公司都可以从中受益(如下图2-9、2-10所示)。 图2-9软件公司与客户项目的关系-让标准与个性化共存 图2-10 软件公司与客户项目的关系-让升级无忧 实现原理 在满足客户个性化定制需求时,传统的方法通常是直接修改标准产品源码,但这样做会带来一个问题:标准产品无法持续升级。相反,无论是在OP模式还是SaaS模式下,Oinone都采用全新的模块为客户进行个性化开发,保持标准产品和个性化模块的独立维护和升级。这是因为在元数据设计时,Oinone采用了面向对象的设计原则,实现了元数据设计与面向对象设计思想的完美融合。 面向对象设计的核心特征包括封装、继承、多态,而Oinone的元数据设计完全融入了这些思想。下面是几个例子,说明Oinone的元数据设计如何体现面向对象设计的核心特征,并带来了什么好处: 继承:在继承原有模型的字段、逻辑、展示的情况下,增加一段代码来扩展模型的字段、逻辑、展示。 多态:在继承原有模型的字段、逻辑、展示的情况下,增加一段代码来覆盖模型的原有字段、逻辑、展示。 封装:外部无需关心模型内部如何实现,只需按照不同场景调用模型对应开放级别的字段、逻辑、展示。 这些特征和优势使得Oinone在满足客户个性化需求时更加灵活和可持续,同时使得标准产品的维护和升级变得更加容易和高效。 在Java语言设计中,万物皆对象,一切都以对象为基础。而Oinone的元数据设计则是以模型为出发点,作为数据和行为的承载体。如下图2-11清晰地描述了Java面向对象编程中封装、继承、多态在Oinone元数据中的对应关系。Oinone元数据描述了B对象继承A对象并拥有其所有属性和方法,并覆盖了A对象的属性1和方法1,同时新增了属性3和方法3。 此外,Oinone的面向对象特性是用元数据来描述的。一方面,我们基于Java编码规范收集相关元数据,以保持不改变Java编程习惯。另一方面,方法和对象的挂载是松耦合的,只要按照元数据规范进行挂载,就能轻松地将其附加到模型上。在不改变原有A对象的情况下,我们可以直接增加方法和属性(如下图2-12所示)。 图2-11 java面向对象在Oinone元数据中对应 图2-12 java对象的修改 VS Oinone元数据模型的修改 Oinone函数不仅支持面向对象的继承和多态特性,还提供了面向切面的拦截器和SPI机制的扩展点,以应对方法逻辑的覆盖和扩展,以及系统层面的逻辑扩展(如下图2-13所示)。这些扩展功能可以独立地在模块中维护。 其中,拦截器可以在不侵入函数逻辑的情况下,根据优先级为满足条件的函数添加执行前和执行后的逻辑。 扩展点是一种类似于SPI机制的逻辑扩展机制,用于扩展函数的逻辑。通过这一机制,可以对函数逻辑进行灵活的扩展,以满足不同的业务需求。 图2-13 Oinone函数拦截与扩展机制 不管是对象、属性还是方法,都可以以独立的模块方式来扩展,这就使得每一个需求都可以成为一个独立的模块,方便我们在研发标准产品时进行模块化的划分,同时也让我们在以低代码模式为客户进行二次开发时,能够更好地支持“标准产品迭代与个性化保持独立”的需求。在2.4.3【oinone独特性之低无一体】一文中,我们也提到了这个特性,但那是在低无一体的情况下,通过元数据融合来实现的。让我们看看基于低代码开发模式下,典型的Oinone二次开发工程结构(如下图2-14所示),就可以更好地理解这个特性啦! 图2-14 Oinone典型的二开工程结构

    2024年5月23日
    1.6K00
  • 3.2 Oinone以模块为组织

    模块(module):是按业务领域划分和管理的最小单元,是一组功能、界面的集合。 带大家快速认识下如何构建一个oinone的模块并启动它。我会从以下几个维度去介绍模块的构建与启动方式、模块详解。让大家直观且全方位地了解oinone的模块所包含的内容 构建第一个Module 启动前端工程 应用中心

    Oinone 7天入门到精通 2024年5月23日
    2.6K00
  • 3.1.2 环境准备(windows版)

    一、后端基础环境准备 安装JDK1.8 ,高于1.8_221以上(下载地址见书籍【附件一】) 打开Windows环境变量配置页 此电脑 => 右键属性 => 系统高级设置 => 环境变量 配置环境变量 在用户环境变量中新建变量为JAVA_HOME的项,值为JDK安装之后的路径 图3-1-27 新建环境变量JAVA_HOM 编辑变量为Path的项添加一个值%JAVA_HOME%\bin 图3-1-28 添加一个环境变量值%JAVA_HOME%\\bin PowerShell或者CMD中验证,输出类似信息为安装配置成功 图3-1-29 验证JAVA是否安装成功 安装 Apache Maven 3.8+ (下载地址见书籍【附件一】) 删除Maven安装目录下的conf/settings.xml 配置mvn的settings,下载附件settings-open.xml,并重命名为settings.xml,建议直接放在【C:\Users\你的用户名.m2】下面。下载地址见oinone开源社区群公告,也可以联系oinone合作伙伴或服务人员 配置环境变量 在用户环境变量中新建变量为M2_HOME的项,值为Maven安装路径 图3-1-30 新建变量M2_HOME 编辑变量为Path的项添加一个值%M2_HOME%\bin 图3-1-31 添加一个值%M2_HOME%\\bin 验证 图3-1-32 验证Maven是否安装成功 安装 Jetbrains IDEA (下载地址见书籍【附件一】) 插件安装 下载地址 密码: mdji 如果IDEA安装了Lombok插件,请禁用Lombok插件 点击菜单项File => Settings => Plugins 图3-1-33 插件管理页面操作示意 下载插件包 (联系Oinone官方客服) 图3-1-35 操作指引 图3-1-36 操作指引 安装MySQL 8 (下载地址见书籍【附件一】) 解压下载的ZIP安装包, 复制到自定义安装目录 设置环境变量 MYSQL_BASE_DIR 提换成MySQL安装目录路径 把 %MYSQL_BASE_DIR%\bin 加入到系统环境变量中 在PowerShell中可以使用 Get-Command mysqld 命令验证环境变量是否配置成功,执行成功输出mysqld的所在路径 初始化 在命令行中执行 mysqld –initialize-insecure –user=mysql 安装 mysqld -install 启动MySQL服务 mysqld -install 设置root用户密码 alter user \’root\’@\’localhost\’ identified with mysql_native_password by \’oinone\’; flush privileges; 安装DB GUI工具 Datagrip、MySQLWorkbench、DBEaver 选其一 ### 安装Git (下载地址见书籍【附件一】) 安装GraphQL测试工具Insomnia(下载地址见书籍【附件一】) 安装RocketMQ(下载地址见书籍【附件一】) 修改安装目录下bin中对应文件的默认配置 a. runserver.cmd文件内容 -Xms2g -Xmx2g为 -Xms1g -Xmx1g b. runbroker.cmd文件内容 -Xms2g -Xmx2g为 -Xms1g -Xmx1g,以及-XX:G1HeapRegionSize=1m 启动RocketMQ NameServer命令 RocketMQ安装目录\bin\mqnamesrv.cmd start 启动RocketMQ Broker命令 RocketMQ安装目录\bin\mqbroker.cmd -n localhost:9876 停止 RocketMQ命令 mqshutdown.cmd broker mqshutdown.cmd namesrv 安装ElasticSearch 版本 8.4.1(下载地址见书籍【附件一】) (非必须) ES运行时需要JDK18及以上版本JDK运行环境, ES安装包中包含了一个JDK18版本 set JAVA_HOME=ES安装路径\jdk 启动 ES安装路径\bin\elasticsearch.bat 停止 ctrl+c 或者关闭cmd、PowerShell的窗口 安装Redis (下载地址见书籍【附件一】) 解压安装包到安装目录 在PowerShell进入到Redis安装目录 在PowerShell中执行.\redis-server.exe,输出图中类似信息(如下图3-35所示): 图3-1-37 验证redis是否安装成功 新开PowerShell窗口, 进入到Redis安装目录, 执行 .\redis-cli.exe回车,输入 ping 回车输出 PONG即表示Redis安装成功 图3-1-38 验证redis是否安装成功 Zookeeper安装(下载地址见书籍【附件一】) 解压安装, 在 PowerShell中执行 tar zxvf 安装包路径(tar.gz包) -C Zk安装目录 进入 Zk安装目录\conf\ 复制 zoo_sample.cfg文件为 zoo.cfg 修改zoo.cfg文件的内容 (其中dataDir需要自己设定) tickTime=2000…

    2024年5月23日
    3.3K00
  • 3.2.1 构建第一个Module

    所有环境准备就绪,就让我们踏上oinone的奇妙之旅吧。先搞个demo模块【展示名为“oinone的Demo工程”,名称为“demoCore”,编码为“demo_core”】试试看,本节学习目的就是能把它启动起来,有个大概的认知。 一、后端工程脚手架 使用如下命令来利用项目脚手架生成启动工程: 新建archetype-project-generate.sh脚本,或者直接下载archetype-project-generate.sh #!/bin/bash # 项目生成脚手架 # 用于新项目的构建 # 脚手架使用目录 # 本地 local # 本地脚手架信息存储路径 ~/.m2/repository/archetype-catalog.xml archetypeCatalog=local # 以下参数以pamirs-demo为例 # 新项目的groupId groupId=pro.shushi.pamirs.demo # 新项目的artifactId artifactId=pamirs-demo # 新项目的version version=1.0.0-SNAPSHOT # Java包名前缀 packagePrefix=pro.shushi # Java包名后缀 packageSuffix=pamirs.demo # 新项目的pamirs platform version pamirsVersion=4.7.8 # Java类名称前缀 javaClassNamePrefix=Demo # 项目名称 module.displayName projectName=OinoneDemo # 模块 MODULE_MODULE 常量 moduleModule=demo_core # 模块 MODULE_NAME 常量 moduleName=DemoCore # spring.application.name applicationName=pamirs-demo # tomcat server address serverAddress=0.0.0.0 # tomcat server port serverPort=8090 # redis host redisHost=127.0.0.1 # redis port redisPort=6379 # 数据库名 db=demo # zookeeper connect string zkConnectString=127.0.0.1:2181 # zookeeper rootPath zkRootPath=/demo mvn archetype:generate \ -DinteractiveMode=false \ -DarchetypeCatalog=${archetypeCatalog} \ -DarchetypeGroupId=pro.shushi.pamirs.archetype \ -DarchetypeArtifactId=pamirs-project-archetype \ -DarchetypeVersion=${pamirsVersion} \ -DgroupId=${groupId} \ -DartifactId=${artifactId} \ -Dversion=${version} \ -DpamirsVersion=${pamirsVersion} \ -Dpackage=${packagePrefix}.${packageSuffix} \ -DpackagePrefix=${packagePrefix} \ -DpackageSuffix=${packageSuffix} \ -DjavaClassNamePrefix=${javaClassNamePrefix} \ -DprojectName="${projectName}" \ -DmoduleModule=${moduleModule} \ -DmoduleName=${moduleName} \ -DapplicationName=${applicationName} \ -DserverAddress=${serverAddress} \ -DserverPort=${serverPort} \ -DredisHost=${redisHost} \ -DredisPort=${redisPort} \ -Ddb=${db} \ -DzkConnectString=${zkConnectString} \ -DzkRootPath=${zkRootPath} 图3-2-1-1 新建archetype-project-generate.sh脚本 Linux/Unix/Mac 需要执行以下命令添加执行权限 chmod +x archetype-project-generate.sh 图3-2-1-2 添加执行权限 根据脚本中的注释修改项目变量(demo工程可无需编辑) 执行脚本 ./archetype-project-generate.sh 二、后端工程结构介绍 通过脚手架生成的demo工程是我们2.4.1【oinone独特性之单体与分布式的灵活切换】一文中介绍的单模块工程结构,属于入门级的一种,麻雀虽小五脏俱全,特别适合新手学习。 结构示意图(如下图3-2-4所示) 图3-2-1-4 结构示意图 工程结构说明 工程名 包名 说明 pamirs-demo-api 对外api包,如果有其他模块需要依赖demo模块,则可以在其pom中引入pamirs-demo-api包 constant 常量的包路径 enumeration 枚举类的包路径 model 该领域核心模型的包路径 service 该领域对外暴露接口api的包路径 tmodel 存放该领域的非存储模型如:用于传输的临时模型 DemoModule 该类是Demo模块的定义 pamirs-demo-boot demo模块的启动类 boot 启动类的包路径 DemoApplication…

    2024年5月23日
    2.8K00
  • 4.5.2 研发辅助之SQL优化

    Oinone体系中是不需要针对模型写SQL的,默认提供了通用的数据管理器。在带来便利的情况下,也导致传统的sql审查就没办法开展。但是我们可以以技术的手段收集慢SQL和限制问题SQL执行。 慢SQL搜集目的:去发现非原则性问题的慢SQL,并进行整改 限制问题SQL执行:对应一些不规范的SQL系统上直接做限制,如果有特殊情况手动放开 一、发现慢SQL 这个功能并没有直接加入到oinone的版本中,需要业务自行写插件,插件代码如下。大家可以根据实际情况进行改造比如: 堆栈入口,例子中只是放了pamirs,可以根据实际情况改成业务包路径 对慢SQL的定义是5s还是3s,根据实际情况变 package pro.shushi.pamirs.demo.core.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; @Intercepts({ @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}) }) @Component @Slf4j public class SlowSQLAnalysisInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); Object result = invocation.proceed(); long end = System.currentTimeMillis(); if (end – start > 10000) {//大于10秒 try { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); StringBuffer slowLog = new StringBuffer(); slowLog.append(System.lineSeparator()); for (StackTraceElement element : stackTraceElements) { if (element.getClassName().indexOf("pamirs") > 0) { slowLog.append(element.getClassName()).append(":").append(element.getMethodName()).append(":").append(element.getLineNumber()).append(System.lineSeparator()); } } Object parameter = null; if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); Configuration configuration = mappedStatement.getConfiguration(); String originalSql = showSql(configuration, boundSql); originalSql = originalSql.replaceAll("\'", "").replace("\"", ""); log.warn("检测到的慢SQL为:" + originalSql); log.warn("业务慢SQL入口为:" + slowLog.toString()); } catch (Throwable e1) { //忽略 } } return result; } public String showSql(Configuration configuration, BoundSql boundSql) { Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); String sql = boundSql.getSql().replaceAll("[\\s]+", " "); if (parameterMappings.size() > 0 && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry…

    Oinone 7天入门到精通 2024年5月23日
    1.8K00

Leave a Reply

Please Login to Comment