多模型联表查询

多模型联表查询

  • 多对一或者一对一关联关系,通过关联模型的字段查询数据

模型结构定义

模型A

@Model(displayName = "A")
@Model.model(A.MODEL_MODEL)
public class A extends IdModel {
    public final static String MODEL_MODEL = "test.A";

    @Field(displayName = "b")
    @Field.many2one
    @Field.Relation(relationFields = {"bId"}, referenceFields = {"id"})
    private B b;

    @Field(displayName = "bId")
    @Field.Integer
    private Long bId;

    @Field(displayName = "B审批状态")
    @Field.Enum
    @Field.Related(related = {"b", "approvalEnum"})
    private ApprovalEnum approvalEnum;
}

模型B

@Model(displayName = "B")
@Model.model(B.MODEL_MODEL)
public class B extends IdModel {
      public final static String MODEL_MODEL = "test.B";

    @Field(displayName = "审批状态")
    @Field.Enum
    private ApprovalEnum approvalEnum;
}

页面设计

在界面设计器中, 设计相对应的表格页面。 A模型related字段拖到搜索栏中。
多模型联表查询

发布页面

自定义Hook

import cz.jirutka.rsql.parser.ast.RSQLOperators;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.framework.connectors.data.sql.AbstractWrapper;
import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper;
import pro.shushi.pamirs.meta.annotation.Hook;
import pro.shushi.pamirs.meta.api.Models;
import pro.shushi.pamirs.meta.api.core.faas.HookBefore;
import pro.shushi.pamirs.meta.api.core.orm.convert.ClientDataConverter;
import pro.shushi.pamirs.meta.api.core.orm.template.context.ModelComputeContext;
import pro.shushi.pamirs.meta.api.dto.config.ModelConfig;
import pro.shushi.pamirs.meta.api.dto.config.ModelFieldConfig;
import pro.shushi.pamirs.meta.api.dto.fun.Function;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.base.D;
import pro.shushi.pamirs.meta.common.spi.Spider;
import pro.shushi.pamirs.meta.domain.model.ModelField;
import pro.shushi.pamirs.meta.enmu.TtypeEnum;
import pro.shushi.pamirs.resource.api.constants.FieldConstants;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 通用 queryData处理。
 */
@Slf4j
@Component
public class QueryDataHook implements HookBefore {

    @Override
    @Hook(priority = 30)
    public Object run(Function function, Object... args) {

        getValueByType(args);

        return function;
    }

    private void getValueByType(Object... args) {

        if (ArrayUtils.isEmpty(args)) {
            return;
        }

        for (int index = 0; index < args.length && null != args[index]; ++index) {
            if (args[index] instanceof AbstractWrapper) {
                AbstractWrapper<?, ?, ?> wrapper = (AbstractWrapper<?, ?, ?>) args[index];
                Map<String, Object> queryData = wrapper.getQueryData();
                if (null == queryData) {
                    continue;
                }
                //有非存储字段
                ModelConfig modelConfig = PamirsSession.getContext().getModelConfig(wrapper.getModel());
                List<ModelFieldConfig> modelFieldConfigs = modelConfig.getModelFieldConfigList();
                Map<String, ModelField> modelFieldMap = new HashMap<>();
                for (ModelFieldConfig modelFieldConfig : modelFieldConfigs) {
                    modelFieldMap.put(modelFieldConfig.getField(), modelFieldConfig.getModelField());
                }
                for (Map.Entry<String, Object> entry : queryData.entrySet()) {
                    String key = entry.getKey();
                    ModelField field = modelFieldMap.get(key);
                    if (field != null && TtypeEnum.isRelatedType(field.getTtype().value())) {
                        //非存储字段是related字段
                        List<String> relateds = field.getRelated();
                        if (relateds != null && relateds.size() == 2) {
                            ModelField reletedField = modelFieldMap.get(relateds.get(0));
                            if (reletedField != null && TtypeEnum.isRelationType(reletedField.getTtype())) {
                                //是关系字段
                                String reletedModelQueryField = relateds.get(1);
                                String referenceModel = reletedField.getReferences();
                                ModelFieldConfig relatedFieldCfg = PamirsSession.getContext().getModelField(referenceModel, reletedModelQueryField);
                                Map<String, Object> relationMap = new HashMap<>();
                                Object value = entry.getValue();
                                relationMap.put(relatedFieldCfg.getField(), value);
                                relationMap.put(FieldConstants._dFieldName, referenceModel);
                                Object relationObj = Spider.getDefaultExtension(ClientDataConverter.class).in(new ModelComputeContext(), referenceModel, relationMap);
                                if (relationObj instanceof D) {
                                    relationMap = ((D) relationObj).get_d();
                                } else {
                                    relationMap = (Map<String, Object>) relationObj;
                                }

                                String column = relatedFieldCfg.getColumn();
                                QueryWrapper<D> wrapper1 = new QueryWrapper<D>();
                                wrapper1.from(referenceModel);
                                wrapper1.eq(column, relationMap.get(relatedFieldCfg.getField()));
                                List<D> values = Models.data().queryListByWrapper(wrapper1);
                                List<String> referenceFields = reletedField.getReferenceFields();

                                StringBuilder rsqlBuilder = new StringBuilder(wrapper.getRsql());
                                for (String refField : referenceFields) {
                                    List<Object> list = new ArrayList<>();
                                    for (D d : values) {
                                        Object v = d.get_d().get(refField);
                                        if (null != v) {
                                            list.add(v);
                                        }
                                    }

                                    if (CollectionUtils.isNotEmpty(list)) {

                                        if (CollectionUtils.size(list) > 10000) {
                                            log.warn("查询数量过多,请自定义查询逻辑,例如使用联表");
                                        }

                                        String vv = list.stream().map(String::valueOf).collect(Collectors.joining(","));

                                        rsqlBuilder.append(" and ")
                                                .append(reletedField.getRelationFields().get(0)).append(RSQLOperators.IN.getSymbol()).append("(").append(vv).append(")")
                                        ;
                                    } else {
                                        rsqlBuilder.append(" and (1!=1)");
                                    }

                                }
                                wrapper.setRsql(rsqlBuilder.toString());
                            }

                        }
                    }
                }
                break;
            }
        }
    }
}

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

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

(0)
yakir的头像yakir数式员工
上一篇 2025年1月9日 pm4:51
下一篇 2025年1月9日 pm8:38

相关推荐

  • 流程设计流程结束通知SPI接口

    1.实现SPI接口 import pro.shushi.pamirs.meta.common.spi.SPI; import pro.shushi.pamirs.meta.common.spi.factory.SpringServiceLoaderFactory; import pro.shushi.pamirs.workflow.app.api.entity.WorkflowContext; import pro.shushi.pamirs.workflow.app.api.model.WorkflowInstance; @SPI(factory = SpringServiceLoaderFactory.class) public interface WorkflowEndNoticeApi { void execute(WorkflowContext context, WorkflowInstance instance); } 自定义通知逻辑 /** * 自定义扩展流程结束时扩展点 */ @Order(999) @Component @SPI.Service public class MyWorkflowEndNoticeApi implements WorkflowEndNoticeApi { @Override public void execute(WorkflowContext context, WorkflowInstance instance) { Long dataBizId = instance.getDataBizId(); //todo自定义逻辑 } }

    2023年12月26日
    1.5K00
  • Oinone请求调用链路

    Oinone请求调用链路 请求格式与简单流程 在Oinone中请求数据存储在请求体中,以GQL的方式进行表示,也就是GQL格式的请求。 当我们发送一个GQL格式的请求,后端会对GQL进行解析,确定想要执行的方法,并对这个方法执行过程中所用到的模型进行构建,最后返回响应。 请求 # 请求路径 pamirs/base http://127.0.0.1:8090/pamirs/base # 请求体内容 query{ petShopProxyBQuery{ sayHello(shop:{shopName:"cpc"}){ shopName } } } 解析 # 简单理解 query 操作类型 petShopProxyBQuery 模块名称 + Query sayHello 方法 fun sayHello() 可以传入参数,参数名为 shop shopName 需要得到的值 响应 # data中的内容 "data": { "petShopQuery": { "hello": { "shopName": "cpc" } } } 具体流程 Oinone是基于SpringBoot的,在Controller中处理请求 会接收所有以 /pamirs 开始的POST请求,/pamirs/后携带的是模块名 @RequestMapping( value = "/pamirs/{moduleName:^[a-zA-Z][a-zA-Z0-9_]+[a-zA-Z0-9]$}", method = RequestMethod.POST ) public String pamirsPost(@PathVariable("moduleName") String moduleName, @RequestBody PamirsClientRequestParam gql, HttpServletRequest request, HttpServletResponse response) { …….. } 整体脉络 第四步执行中有两大重要的步骤,一步是动态构建GQL,一步是执行请求。 动态构建GQL 请求执行

    2024年12月1日
    1.3K00
  • Oinone登录扩展:对接SSO(适应于4.7.8及之后的版本)

    适配版本 4.7.8及其之后的版本 概述 在企业内部,对于已有一套完整的登录系统(SSO)的情况下,通常会要求把所有的系统都对接到SSO中;本文主要讲解用Oinone开发的项目对接SSO的具体实现。 对接步骤 1、项目自定义实现UserCookieLogin,可参考示例说明: pro.shushi.pamirs.user.api.login.UserCookieLoginFree 2、对接SSO示例 对接流程说明: 1)【必须】从请求头Header或者Query中获取到token; 2)【必须】去SSO服务端验证token的有效性; 3)【可选】根据token去服务端获取用户信息;如果token可以直接反解析出用户信息,则该步骤忽略; 4)【可选】根据实际情况用户信息是否进行DB的存储; 5)【必须】验证token有效后,生成Session和Cookie(即token换cookie); 注意超时时间需要 <= SSO服务端token失效时间。 package pro.shushi.pamirs.demo.core.sso; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import pro.shushi.pamirs.demo.core.sso.constant.HttpConstant; import pro.shushi.pamirs.demo.core.sso.constant.SessionUserTypeEnum; import pro.shushi.pamirs.demo.core.sso.model.ApiCommonTransient; import pro.shushi.pamirs.demo.core.sso.model.PermissionInfoResp; import pro.shushi.pamirs.demo.core.sso.utils.AuthenticateUtils; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.model.PamirsUserDTO; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils; import pro.shushi.pamirs.resource.api.enmu.UserSignUpType; import pro.shushi.pamirs.user.api.cache.UserCache; import pro.shushi.pamirs.user.api.constants.UserConstant; import pro.shushi.pamirs.user.api.enmu.UserExpEnumerate; import pro.shushi.pamirs.user.api.enmu.UserLoginTypeEnum; import pro.shushi.pamirs.user.api.login.IUserLoginChecker; import pro.shushi.pamirs.user.api.login.UserCookieLogin; import pro.shushi.pamirs.user.api.login.UserCookieLoginSimple; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient; import pro.shushi.pamirs.user.api.service.UserService; import pro.shushi.pamirs.user.api.utils.CookieUtil; import javax.servlet.http.HttpServletResponse; /** * * @author shushi * * 完全自定义login的过程 * 需要实现登陆部分login 以及拦截部分fetchUserIdByReq * 如果fetchUserIdByReq返回值为null的时候 将会被拦截 */ @Slf4j @Order(0) @Component public class DemoUserSSOCookieLogin extends UserCookieLogin<PamirsUser> { //刷新令牌 private static String REFRESH_TOKEN = "refreshToken"; //系统id private static String CLIENT_ID = "client-id"; //访问令牌 private static String AUTHORIZATION = "Authorization"; private IUserLoginChecker checker; @Autowired private UserService userService; @Autowired private RedisTemplate<String, String> redisTemplate; @Override public String type() { return UserLoginTypeEnum.COOKIE.value(); } @Override public PamirsUser resolveAndVerification(PamirsUserTransient user) { if (checker == null) { checker = BeanDefinitionUtils.getBean(IUserLoginChecker.class); } return checker.check4login(user); } /** * 重写登录拦截功能 * 该函数主要作用,通过三方权限校验. * @return */ // 版本升级需要修改 @Override public PamirsUserDTO fetchUserIdByReq() { String sessionId =…

    2024年4月2日
    2.2K00
  • 工作流引入流程概览与流程监控

    流程概览依赖说明 使用 流程概览 功能前,需要在项目中引入 pamirs-workflow-datavi-core、 pamirs-data-visualization-core依赖,并启动datavi模块: <dependency> <groupId>pro.shushi.pamirs.workflow</groupId> <artifactId>pamirs-workflow-datavi-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.data.visualization</groupId> <artifactId>pamirs-data-visualization-core</artifactId> </dependency> 警告: 在 oinone 平台启用「流程概览」能力时,应用启动模块一旦引入 pamirs-workflow-api/core,必须同时引入 pamirs-workflow-datavi-api/core。在多启动模块架构下,严禁出现仅部分启动模块引入 pamirs-workflow-core 而未引入 pamirs-workflow-datavi-core 的情况,否则将导致流程概览相关元数据计算异常,出现删表等情况。 流程概览配置项 流程概览页面内置缓存机制,可通过配置项调整缓存刷新周期及图表展示的数据条数: pamirs: workflow: dashboard: cache-time: 10 # 流程概览缓存刷新时间(单位:分钟),默认 10 分钟 page-size: 10 # 流程运行分析中 4 个图表的展示数量,默认查询前 10 条数据 统计指标说明 引入 pamirs-workflow-datavi-core 依赖后,系统会按照以下规则进行数据同步: 当日数据同步:每小时同步一次当日数据; 昨日数据同步:次日凌晨同步前一日数据。 由于在引入依赖后才会开始执行数据同步,统计指标页提供了「同步」按钮,可用于对历史数据进行补采。即使不执行历史同步,也不会影响核心业务流程,仅会影响统计数据和图表的展示效果。 统计指标数据主要用于 支撑 流程概览 和 流程监控 中的统计图表展示; 为数据分析与可视化提供基础数据。 上述统计数据对工作流的审批、流转等核心业务无任何影响。如有需要,也可以基于流程监控的数据,配合数据可视化设计器,自定义构建符合业务需求的展示页面。

    2025年11月17日
    42900

Leave a Reply

登录后才能评论