非存储字段搜索,适应灵活的搜索场景

1、非存储字段搜索

1.1 描述

通常根据本模型之外的信息作为搜索条件时,通常会把 这些字段放在代理模型上。这类场景我们称之为 非存储字段搜索

1.2 场景一

非存储字段为基本的String。
1.代码定义:非存储字段为基本的包装数据类型

    @Field(displayName = "确认密码", store = NullableBoolEnum.FALSE)
    private String confirmPassword;

2.设计器拖拽:列表中需要有退拽这个字段,字段是否隐藏的逻辑自身业务是否需要决定。
image.png
3.页面通过非存储字段为基本的包装数据类型进行搜索时。会拼在 queryWrapper 的属性queryData中,queryData为Map,key为字段名,value为搜索值。
image.png
4.后台逻辑处理代码示例:

        Map<String, Object> queryData = queryWrapper.getQueryData();
        if (null != queryData) {
            Object productIdObj = queryData.get(PRODUCT_ID);
            if (Objects.nonNull(productIdObj)) {
                String productId = productIdObj.toString();
                queryWrapper.lambda().eq(MesProduceOrderProxy::getProductId, productId);
            }
        }

1.3 场景二

非存储字段为非存储对象。

  1. 定义 为非存储的
    @Field(displayName = "款", store = NullableBoolEnum.FALSE)
    @Field.many2one
    @Field.Relation(store = false)
    private MesProduct product;

2.页面在搜索栏拖拽非存储字段作为搜索条件

image.png

3.后台逻辑处理代码示例:

      try {
            if (null != queryData && !queryData.isEmpty()) {
                List<Long> detailId = null;
                BasicSupplier supplier = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(supplierField), BasicSupplier.class);
                MesProduct product = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(productField), MesProduct.class);
                MesMaterial material = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(materialField), MesMaterial.class);
                if (supplier != null) {
                    detailId = bomService.queryBomDetailIdBySupplierId(supplier.getId());
                    if (CollectionUtils.isEmpty(detailId)) {
                        detailId.add(-1L);
                    }
                }

                if (product != null) {
                    List<Long> produceOrderId = produceOrderService.queryProductOrderIdByProductIds(product.getId());
                    if (CollectionUtils.isNotEmpty(produceOrderId)) {
                        queryWrapper.lambda().in(MesProduceBomSizes::getProduceOrderId, produceOrderId);
                    }
                }

                if (material != null) {
                    //找出两个bom列表的并集
                    List<Long> materBomDetailId = bomService.queryBomDetailIdByMaterialId(material.getId());
                    if (CollectionUtils.isNotEmpty(detailId)) {
                        detailId = detailId.stream().filter(materBomDetailId::contains).collect(Collectors.toList());
                    } else {
                        detailId = new ArrayList<>();
                        if (CollectionUtils.isEmpty(materBomDetailId)) {
                            detailId.add(-1L);
                        } else {
                            detailId.addAll(materBomDetailId);
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(detailId)) {
                    queryWrapper.lambda().in(MesProduceBomSizes::getProductBomId, detailId);
                }
            }
        } catch (Exception e) {
            log.error("queryData处理异常", e);
        }

注意: 如果这样定义

    @Field(displayName = "款",store = NullableBoolEnum.FALSE)
    @Field.Relation(relationFields = "produceId", referenceFields = "id",store = false)
    @Field.many2one
    private MesProduct product;

    @Field(displayName = "款Id",store = NullableBoolEnum.FALSE)
    private Long produceId;

搜索时 produceId 选择product 搜索是 produceId 会被拼接在QueryWrapper 的Rsql中

Rsql解析类

pro.shushi.pamirs.framework.gateways.rsql.RSQLHelper

Rsql参考代码

/**
     * Rsql解析 将属性字段值从QueryWrapper中originRsql属性值的Rsql解出来
     * 将原有条件替换成 ’1‘==’1‘
     *
     * @param queryWrapper 查询Wrapper
     * @param fields       需要解析出来的属性字段列表
     * @param valeMap      属性对应值的Map
     * @return 返回生产单Id列表
     */

    public static QueryWrapper convertWrapper(QueryWrapper queryWrapper, List<String> fields, Map<String, Object> valeMap) {
        if (StringUtils.isNotBlank(queryWrapper.getOriginRsql())) {
            String rsql = RSQLHelper.toTargetString(RSQLHelper.parse(queryWrapper.getModel(), queryWrapper.getOriginRsql()), new RSQLNodeConnector() {
                @Override
                public String comparisonConnector(RSQLNodeInfo nodeInfo) {
                    //判断字段为unStore,则进行替换
                    String field = nodeInfo.getField();
                    if (fields.contains(field)) {
                        valeMap.put(field, nodeInfo.getArguments().get(0));
                        RSQLNodeInfo newNode = new RSQLNodeInfo(nodeInfo.getType());
                        //设置查询字段为name
                        newNode.setField("1");
                        newNode.setOperator(RsqlSearchOperation.EQUAL.getOperator());
                        newNode.setArguments(Collections.singletonList("1"));
                        return super.comparisonConnector(newNode);
                    }
                    return super.comparisonConnector(nodeInfo);
                }
            });
            queryWrapper = Pops.f(Pops.query().from(queryWrapper.getModel())).get();
            //把RSQL转换成SQL
            String sql = RsqlParseHelper.parseRsql2Sql(queryWrapper.getModel(), rsql);
            if (StringUtils.isNotBlank(sql)) {
                queryWrapper.apply(sql);
            }
            return queryWrapper;
        }
        return queryWrapper;
    }

Oinone社区 作者:shao原创文章,如若转载,请注明出处:https://doc.oinone.top/dai-ma-shi-jian/5592.html

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

(0)
shao的头像shao数式管理员
上一篇 2024年2月20日 pm6:46
下一篇 2024年2月20日 pm6:52

相关推荐

  • 后端:如何自定义表达式实现特殊需求?扩展内置函数表达式

    平台提供了很多的表达式,如果这些表达式不满足场景?那我们应该如何新增表达式去满足项目的需求?目前平台支持的表达式内置函数,参考 1. 扩展表达式的场景 注解@Validation的rule字段支持配置表达式校验如果需要判断入参List类型字段中的某一个参数进行NULL校验,发现平台的内置函数不支持该场景的配置,这里就可以通过平台的机制,对内置函数进行扩展。 常见的一些代码场景,如下: package pro.shushi.pamirs.demo.core.action; ……引用类 @Model.model(PetShopProxy.MODEL_MODEL) @Component public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> { @Override protected PetShopProxy fetchData(PetShopProxy data) { return data.queryById(); } @Validation(ruleWithTips = { @Validation.Rule(value = "!IS_BLANK(data.code)", error = "编码为必填项"), @Validation.Rule(value = "LEN(data.name) < 128", error = "名称过长,不能超过128位"), }) @Action(displayName = "启用") @Action.Advanced(invisible="!(activeRecord.code !== undefined && !IS_BLANK(activeRecord.code))") public PetShopProxy dataStatusEnable(PetShopProxy data){ data = super.dataStatusEnable(data); data.updateById(); return data; } ……其他代码 } 2. 新建一个自定义表达式的函数 校验入参如果是个集合对象的情况下,单个对象的某个字段如果为空,返回false的函数。 例子:新建一个CustomCollectionFunctions类 package xxx.xxx.xxx; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.common.constants.NamespaceConstants; import pro.shushi.pamirs.meta.util.FieldUtils; import java.util.List; import static pro.shushi.pamirs.meta.enmu.FunctionCategoryEnum.COLLECTION; import static pro.shushi.pamirs.meta.enmu.FunctionLanguageEnum.JAVA; import static pro.shushi.pamirs.meta.enmu.FunctionOpenEnum.LOCAL; import static pro.shushi.pamirs.meta.enmu.FunctionSceneEnum.EXPRESSION; /** * 自定义内置函数 */ @Fun(NamespaceConstants.expression) @Component public class CustomCollectionFunctions { /** * LIST_FIELD_NULL 就是我们自定义的表达式,不能与已经存在的表达式重复!!! * * @param list * @param field * @return */ @Function.Advanced( displayName = "校验集成的参数是否为null", language = JAVA, builtin = true, category = COLLECTION ) @Function.fun("LIST_FIELD_NULL") @Function(name = "LIST_FIELD_NULL", scene = {EXPRESSION}, openLevel = LOCAL, summary = "函数示例: LIST_FIELD_NULL(list,field),函数说明: 传入一个对象集合,校验集合的字段是否为空" ) public Boolean listFieldNull(List list, String field) { if (null == list) { return false; } if (CollectionUtils.isEmpty(list)) { return false; } for (Object data : list) { Object value =…

    2024年5月30日
    2.2K00
  • 动态视图支持权限配置

    动态页面在前端自定义之后,目前是没办法通过权限去控制路由页面的权限的,本篇文章将介绍如何解决这个问题。阅读本篇文章之前,应已经实现自定义页面渲染动态视图。参考文章:自定义视图内部渲染动态视图 实现思路: 通过自定义页面里设计的组件api名字,获取配置的路由页面名字。 解析路由页面包含的所有动作,拼接权限节点。 将路由页面的权限节点,拼到自定义视图所在菜单的权限管理上。 效果,路由页面的动作出现在自定义视图所在的菜单权限节点上 代码示例 package pro.shushi.pamirs.top.core.auth; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.entity.node.ActionPermissionNode; import pro.shushi.pamirs.auth.api.entity.node.PermissionNode; import pro.shushi.pamirs.auth.api.extend.load.PermissionNodeLoadExtendApi; import pro.shushi.pamirs.auth.api.loader.visitor.AuthCompileContext; import pro.shushi.pamirs.auth.api.loader.visitor.AuthCompileVisitor; import pro.shushi.pamirs.auth.api.loader.visitor.DslParser; import pro.shushi.pamirs.auth.api.pmodel.AuthResourceAuthorization; import pro.shushi.pamirs.auth.api.utils.AuthAuthorizationHelper; import pro.shushi.pamirs.boot.base.model.Menu; import pro.shushi.pamirs.boot.base.model.View; import pro.shushi.pamirs.boot.base.model.ViewAction; import pro.shushi.pamirs.boot.base.ux.model.UIView; import pro.shushi.pamirs.boot.base.ux.model.UIWidget; import pro.shushi.pamirs.boot.base.ux.model.view.UIField; import pro.shushi.pamirs.boot.web.loader.path.AccessResourceInfo; import pro.shushi.pamirs.boot.web.loader.path.ResourcePath; import pro.shushi.pamirs.boot.web.manager.MetaCacheManager; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.common.spi.SPI; import pro.shushi.pamirs.top.api.TopModule; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @Component @Order(88) @SPI.Service @Slf4j public class MyTestNodeLoadExtend implements PermissionNodeLoadExtendApi { @Autowired protected MetaCacheManager metaCacheManager; @Override public List<PermissionNode> buildNextPermissions(PermissionNode selected, List<PermissionNode> nodes) { // 动态页面所属的菜单name List<String> menuNames = new ArrayList<>(); menuNames.add("uiMenu3c094a75bd88461ba0ad780825069b32"); // 自定义组件api名称 List<String> apiNames = new ArrayList<>(); apiNames.add("dynamicView"); List<ActionPermissionNode> newNodes = new ArrayList<>(); for (String menuName : menuNames) { List<ActionPermissionNode> actionPermissionNodes = buildActionPermissionNodes(selected, menuName, apiNames); if (CollectionUtils.isNotEmpty(actionPermissionNodes)) { newNodes.addAll(actionPermissionNodes); } } nodes.addAll(newNodes); return nodes; } private List<ActionPermissionNode> buildActionPermissionNodes(PermissionNode selected, String menuName, List<String> apiNames) { String path = ResourcePath.generatorPath(TopModule.MODULE_MODULE, menuName); if (!path.equals(selected.getPath())) { return null; } Menu menu = metaCacheManager.fetchCloneMenus(TopModule.MODULE_MODULE).stream() .filter(v -> v.getName().equals(menuName)) .findFirst() .orElse(null); if (menu == null) { return null; } menu.fieldQuery(Menu::getViewAction); View mainView = fetchMainView(menu.getViewAction()); if (mainView == null) { return null; }…

    2025年9月10日
    72700
  • 集成设计器数据流程、流程设计器可以暴露接口触发

    集成设计器数据流程暴露接口触发 需求:在集成设计器配置的连接器、数据流程链接到外部接口,需要可以有一个管理页面,统一管理这些集成配置。比如对接多个医院的挂号系统,希望可以配置好数据流程之后,能够在已经实现的开放接口上,动态的调用集成平台配置的数据流程。 连接器暴露接口触发 设计连接器资源配置模型。使用业务ID+接口唯一标识+连接器实现连接器资源和业务唯一。 @Model.model(EipConnectorResourceSetting.MODEL_MODEL) @Model(displayName = "连接器资源配置", summary = "连接器资源配置") @Model.Advanced(unique = {"hospitalId, interfaceUnique"}) public class EipConnectorResourceSetting extends IdModel { public static final String MODEL_MODEL = "hr.simple.EipConnectorResourceSetting"; @UxTableSearch.FieldWidget(@UxWidget()) @Field(displayName = "医院", required = true) @Field.Relation(relationFields = {"hospitalId"}, referenceFields = {"id"}) private Hospital hospital; @Field.Integer @Field(displayName = "医院ID", invisible = true) private Long hospitalId; @UxTableSearch.FieldWidget(@UxWidget()) @Field.String @Field(displayName = "接口唯一标识", required = true) private String interfaceUnique; @UxTableSearch.FieldWidget(@UxWidget()) @Field.many2one @Field(displayName = "连接器", required = true) @Field.Relation(relationFields = {"connectorId"}, referenceFields = {"id"}) private EipConnector connector; @Field.Integer @Field(displayName = "连接器ID", invisible = true) private Long connectorId; @UxTableSearch.FieldWidget(@UxWidget()) @Field.many2one @Field(displayName = "连接器接口", required = true) @Field.Relation(relationFields = {"integrationInterfaceId"}, referenceFields = {"id"}, domain = "connectorId==${activeRecord.connector.id}") private EipConnectorResource connectorResource; @Field.Integer @Field(displayName = "连接器接口ID", invisible = true) private Long connectorResourceId; } 开放接口定义,文档参考:https://doc.oinone.top/oio4/9326.html外部平台调用开放接口,实现动态调用连接器资源 @Model.model(HospitalSection.MODEL_MODEL) @Model(displayName = "医院科室", summary = "医院科室") public class HospitalSection extends IdModel { public static final String MODEL_MODEL = "net.example.HospitalSection"; @Field(displayName = "科室Code", required = true) private String deptCode; @Field(displayName = "科室名称", required = true) private String deptName; @Field(displayName = "科室描述") private String deptDesc; } @Slf4j @Fun @Component public class TestOpenService { @Resource private TestCommonService testCommonService;…

    2025年4月21日
    53900
  • 如何在代码中使用自增ID和获取序列

    在使用继承IDModel或CodeModel时,id和code是系统默认自动生成, 默认值规则:ID–>分布式ID; CODE–>根据定义的SequenceConfig规则自动生成。 在特定情况下需要落库前先生成ID或者Code,这些场景下可参照如下代码示例 一、使用自增ID 单个字段设置方式 // 主键字段,可以使用mysql的自增能力 @Field.Integer @Field.PrimaryKey(keyGenerator = KeyGeneratorEnum.AUTO_INCREMENT) @Field.Advanced(batchStrategy = FieldStrategyEnum.NEVER) @Field(displayName = "id", summary = "Id字段,⾃增") private Long id; @Field.Integer @Field(displayName = "自增版本") @Field.Sequence(sequence = "SEQ", initial = 1) private Long version; 全局设置方式 该方式会作用到每一个存储模型的id字段,在application.yml配置文件中修改id的生成规则,查找配置项关键字key-generator,默认为DISTRIBUTION(分布式id),可修改为 AUTO_INCREMENT(自增id) 二、手动方式获取序列 获取方式示例1 /** * 在特定场景下需要手动生成Id或者code时,可参照这个示例 */ public void manualSetIdCode(){ DemoItem demoItem = new DemoItem(); //手动生成ID和code Object idObj = Spider.getDefaultExtension(IdGenerator.class).generate(PamirsTableInfo.fetchKeyGenerator(DemoItem.MODEL_MODEL)); demoItem.setId(TypeUtils.createLong(idObj)); Object codeObj = CommonApiFactory.getSequenceGenerator().generate("SEQ",DemoItem.MODEL_MODEL); String code = TypeUtils.stringValueOf(codeObj); demoItem.setCode(code); //…… } 获取方式示例2 1、在系统启动的时候初始化SequenceConfig 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.extend.MetaDataEditor; import pro.shushi.pamirs.core.common.InitializationUtil; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.constant.SeqConstants; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.meta.Meta; import pro.shushi.pamirs.meta.enmu.SequenceEnum; import java.util.Map; /** * DemoMetadataEditor */ @Slf4j @Component public class DemoMetadataEditor implements MetaDataEditor { @Override public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) { InitializationUtil util = InitializationUtil.get(metaMap, DemoModule.MODULE_MODULE, DemoModule.MODULE_NAME); if (util == null) { log.error("获取初始化序列失败"); return; } bizSequence(util); } private void bizSequence(InitializationUtil util) { util.createSequenceConfig("申请单编码生成", SeqConstants.NABEL_SAMPLE_APPLY_SEQ, SequenceEnum.ORDERLY_SEQ, 8) .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); util.createSequenceConfig("订单编码生成", SeqConstants.NABEL_SAMPLE_ORDER_SEQ_YP, SequenceEnum.ORDERLY_SEQ, 8) .setPrefix("YP") .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); } } 2、在代码中使用序列 public static String getSaleOrderCode() { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_STRUCTURE_SEQ); return TypeUtils.stringValueOf(sequence); } public static String getApplyOrderCode(String prefix) { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_APPLY_SEQ); return…

    2024年5月25日
    1.8K00
  • 读写分离

    总体介绍 Oinone的读写分离方案是基于Sharding-JDBC的整合方案,要先具备一些Sharding-JDBC的知识。 [Sharding-JDBC] 读写分离依赖于主从复制来同步数据,从库复制数据后,才能通过读写分离策略将读请求分发到从库,实现读写操作的分流,请根据业务需求自行实现主从配置。 配置读写策略 配置 top_demo 模块走读写分离的逻辑数据源 pamirsSharding。 配置数据源。 为 pamirsSharding 配置数据源以及 sharding 规则。 指定需要被sharding-jdbc接管的模块 指定top_demo模块给 Sharding-JDBC 接管,接管逻辑数据源名为 pamirsSharding pamirs: framework: data: ds-map: base: base top_demo: pamirsSharding 配置数据源 pamirs: datasource: pamirsMaster: driverClassName: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/61_pamirs_mydemo_master?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: root password: ma123456 initialSize: 5 maxActive: 200 minIdle: 5 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true asyncInit: true pamirsSlaver: # 从库数据源配置 driverClassName: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/61_pamirs_mydemo_slaver?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: root password: ma123456 initialSize: 5 maxActive: 200 minIdle: 5 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true asyncInit: true 配置读写数据源及规则 pamirs: sharding: define: data-sources: pamirsSharding: pamirsMaster # 为逻辑数据源pamirsSharding指向主数据源pamirsMaster。 models: "[trigger.PamirsSchedule]": tables: 0..13 rule: pamirsSharding: actual-ds: # 指定逻辑数据源pamirsSharding代理的数据源为pamirsMaster、pamirsSlaver – pamirsMaster – pamirsSlaver # 以下配置跟sharding-jdbc配置一致 replicaQueryRules: – data-sources: pamirsSharding: primaryDataSourceName: pamirsMaster # 写库数据源 replicaDataSourceNames: – pamirsSlaver # 读库数据源 loadBalancerName: round_robin load-balancers: round_robin: type: ROUND_ROBIN # 读写规则

    2025年5月22日
    45300

Leave a Reply

登录后才能评论