Oinone请求路由源码分析

通过源码分析,从页面发起请求,如果通过graphQL传输到具体action的链路,并且在这之间做了哪些隐式处理
分析源码版本5.1.x

请求流程大致如下:

  1. 拦截所有指定的请求
  2. 组装成graphQL请求信息
  3. 调用graphQL执行
  4. 通过hook拦截先执行
    • RsqlDecodeHook:rsql解密
    • UserHook: 获取用户信息, 通过cookies获取用户ID,再查表获取用户信息,放到本地Local线程里
    • RoleHook: 角色Hook
    • FunctionPermissionHook: 函数权限Hook ,跳过权限拦截的实现放在这一层,对应的配置
    •  pamirs:
           auth:
              fun-filter:
                    - namespace: user.PamirsUserTransient
                      fun: login #登录
                    - namespace: top.PetShop
                      fun: action
                          
    • DataPermissionHook: 数据权限hook
    • PlaceHolderHook:占位符转化替换hook
    • RsqlParseHook: 解释Rsql hook
    • SingletonModelUpdateHookBefore
  5. 执行post具体内容
  6. 通过hook拦截后执行
    • QueryPageHook4TreeAfter: 树形Parent查询优化
    • FieldPermissionHook: 字段权限Hook
    • UserQueryPageHookAfter
    • UserQueryOneHookAfter
  7. 封装执行结果信息返回

时序图

Oinone请求路由源码分析
时序图

核心源码解析

拦截所有指定的请求 /pamirs/模块名
RequestController

    @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) {
                             }

DefaultRequestExecutor 构建graph请求信息,并调用graph请求


() -> execute(GraphQL::execute, param), param
private <T> T execute(BiFunction<GraphQL, ExecutionInput, T> executor, PamirsRequestParam param) {
    // 获取GraphQL请求信息,包含grapsh schema
    GraphQL graphQL = buildGraphQL(param);
...
    ExecutionInput executionInput = ExecutionInput.newExecutionInput()
                .query(param.getQuery())
                .variables(param.getVariables().getVariables())
                .dataLoaderRegistry(Spider.getDefaultExtension(DataLoaderRegistryApi.class).dataLoader())
                .build();
...
// 调用 GraphQL的方法execute 执行
    T result = executor.apply(graphQL, executionInput);
...
    return result;
    }

QueryAndMutationBinder 绑定graphQL读取写入操作

public static DataFetcher<?> dataFetcher(Function function, ModelConfig modelConfig) {
        if (isAsync()) {
            if (FunctionTypeEnum.QUERY.in(function.getType())) {
                return AsyncDataFetcher.async(dataFetchingEnvironment -> dataFetcherAction(function, modelConfig, dataFetchingEnvironment), ExecutorServiceApi.getExecutorService());
            } else {
                return dataFetchingEnvironment -> dataFetcherAction(function, modelConfig, dataFetchingEnvironment);
            }
        } else {
            return dataFetchingEnvironment -> dataFetcherAction(function, modelConfig, dataFetchingEnvironment);
        }
    }

    private static Object dataFetcherAction(Function function, ModelConfig modelConfig, DataFetchingEnvironment environment) {
        try {
            SessionExtendUtils.tagMainRequest();
            // 使用共享的请求和响应对象
            return Spider.getDefaultExtension(ActionBinderApi.class)
                    .action(modelConfig, function, FunctionTypeEnum.QUERY.in(function.getType()), environment);
        } finally {
            PamirsSession.clearMainSession();
        }
    }

FunctionManager 执行具体方法,会执行前后hook

public Object runProxy(java.util.function.Function<Object[], Object> consumer, Function function, Object... args) {
        SessionMetaBit directive = Objects.requireNonNull(PamirsSession.directive());
        if (directive.isFromClient() || directive.isBuiltAction()) {
            directive.disableFromClient();
            boolean isHook = directive.isHook();
            boolean isDoExtPoint = directive.isDoExtPoint();
            if (isHook) {
                // 执行权限,用户相关的hook
        hookApi.before(function.getNamespace(), function.getFun(), args);
            }
            // 执行业务方法
            Object result = Tx.build(function.getNamespace(), function.getFun()).execute((transactionStatus) -> {
                Object txResult;
                // 植入预制结果集
                String supplier = "ctxHack" + function.getNamespace() + function.getFun();
                Object supplierObj = Optional.ofNullable(PamirsSession.getRequestVariables())
                        .map(PamirsRequestVariables::getVariables)
                        .map(_variables -> _variables.get(supplier))
                        .orElse(null);
                if (null != supplierObj) {
                    try {
                        txResult = ((Supplier<?>) supplierObj).get();
                    } finally {
                        Optional.ofNullable(PamirsSession.getRequestVariables())
                                .map(PamirsRequestVariables::getVariables)
                                .ifPresent(_variables -> _variables.remove(supplier));
                    }
                } else if (isDoExtPoint) {
                    Object[] tArgs = ArrayUtils.toArray(builtinExtPointExecutor.before(function, args));
                    txResult = builtinExtPointExecutor.override(function, consumer, tArgs);
                    builtinExtPointExecutor.callback(function, args);
                    txResult = builtinExtPointExecutor.after(function, txResult);
                } else {
                    txResult = consumer.apply(args);
                }
                return txResult;
            });
            if (isHook) {
                // 执行
                hookApi.after(function.getNamespace(), function.getFun(), result);
            }
            return result;
        } else {
            return Tx.build(function.getNamespace(), function.getFun())
                    .execute((transactionStatus) -> consumer.apply(args));
        }
    }

默认hook 逻辑

  1. RsqlDecodeHook:rsql解密
  2. UserHook:获取用户信息, 通过cookies获取用户ID,再查表获取用户信息,放到本地Local线程里
  3. RoleHook: 角色Hook
  4. FunctionPermissionHook: 函数权限Hook,跳过权限拦截
  5. DataPermissionHook: 数据权限hook
  6. PlaceHolderHook:占位符转化替换hook
  7. RsqlParseHook: 解释Rsql hook
  8. SingletonModelUpdateHookBefore
  9. QueryPageHook4TreeAfter: 树形Parent查询优化
  10. FieldPermissionHook: 字段权限Hook
  11. UserQueryPageHookAfter
  12. UserQueryOneHookAfter

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

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

(1)
oinone的头像oinone
上一篇 2024年8月20日 pm9:09
下一篇 2024年8月21日 pm10:33

相关推荐

  • 分库分表与自定义分表规则

    总体介绍 Oinone的分库分表方案是基于Sharding-JDBC的整合方案,要先具备一些Sharding-JDBC的知识。[Sharding-JDBC]https://shardingsphere.apache.org/document/current/cn/overview/ 做分库分表前,大家要有一个明确注意的点就是分表字段(也叫均衡字段)的选择,它是非常重要的,与业务场景非常相关。在明确了分库分表字段以后,甚至在功能上都要做一些妥协。比如分库分表字段在查询管理中做为查询条件是必须带上的,不然效率只会更低。 分表字段不允许更新,所以代码里更新策略设置类永不更新,并在设置了在页面修改的时候为readonly 配置分表策略 配置ShardingModel模型走分库分表的数据源pamirsSharding 为pamirsSharding配置数据源以及sharding规则 a. pamirs.sharding.define用于oinone的数据库表创建用 b. pamirs.sharding.rule用于分表规则配置 为pamirsSharding配置数据源以及sharding规则 1)指定模型对应数据源 pamirs: framework: system: system-ds-key: base system-models: – base.WorkerNode data: default-ds-key: pamirs ds-map: base: base modelDsMap: "[demo.ShardingModel]": pamirsSharding #配置模型对应的库 2)分库分表规则配置 pamirs: sharding: define: data-sources: ds: pamirs pamirsSharding: pamirs #申明pamirsSharding库对应的pamirs数据源 models: "[trigger.PamirsSchedule]": tables: 0..13 "[demo.ShardingModel]": tables: 0..7 table-separator: _ rule: pamirsSharding: #配置pamirsSharding库的分库分表规则 actual-ds: – pamirs #申明pamirsSharding库对应的pamirs数据源 sharding-rules: # Configure sharding rule ,以下配置跟sharding-jdbc配置一致 – tables: demo_core_sharding_model: #demo_core_sharding_model表规则配置 actualDataNodes: pamirs.demo_core_sharding_model_${0..7} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: table_inline shardingAlgorithms: table_inline: type: INLINE props: algorithm-expression: demo_core_sharding_model_${(Long.valueOf(user_id) % 8)} props: sql.show: true 自定义规则 默认规则即通用的分库分表策略,如按照数据量、哈希等方式进行分库分表;通常默认规则是可以的。 但在一些复杂的业务场景下,使用默认规则可能无法满足需求,需要根据实际情况进行自定义。例如,某些业务可能有特定的数据分布模式或者查询特点,需要定制化的分库分表规则来优化数据访问性能或者满足业务需求。在这种情况下,使用自定义规则可以更好地适应业务的需求。 自定义分表规则示例 示例1:按月份分表(DATE_MONTH ) package pro.shushi.pamirs.demo.core.sharding; import cn.hutool.core.date.DateUtil; import com.google.common.collect.Range; import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import java.util.*; /** * @author wangxian * @version 1.0 * @description */ @Component @Slf4j public class DateMonthShardingAlgorithm implements StandardShardingAlgorithm<Date> { private Properties props; @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> preciseShardingValue) { Date date = preciseShardingValue.getValue(); String suffix = "_" + (DateUtil.month(date) + 1); for (String tableName : availableTargetNames) { if (tableName.endsWith(suffix)) { return tableName; } } throw new IllegalArgumentException("未找到匹配的数据表"); } @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> rangeShardingValue) { List<String> list =…

    2024年5月11日
    1.5K00
  • 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.6K00
  • 如何重写获取首页的方法

    介绍 用户登录成功后或者访问网页不带任何路由参数的时候前端会请求全局的首页的视图动作viewAction配置,然后跳转到该视图动作viewAction 方案 我们可以通过在该方法的后置hook自定义获取首页的逻辑,下面以按角色跳转不同首页的需求示例 package pro.shushi.pamirs.demo.core.hook; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.model.AuthRole; import pro.shushi.pamirs.boot.base.enmu.BaseExpEnumerate; import pro.shushi.pamirs.boot.base.model.ViewAction; import pro.shushi.pamirs.boot.web.loader.PageLoadAction; import pro.shushi.pamirs.demo.api.model.DemoItemCategory; import pro.shushi.pamirs.demo.api.model.DemoItemLabel; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.CommonApiFactory; import pro.shushi.pamirs.meta.api.core.faas.HookAfter; import pro.shushi.pamirs.meta.api.dto.fun.Function; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.user.api.model.PamirsUser; import java.util.List; import java.util.stream.Collectors; @Component public class DemoHomepageHook implements HookAfter { private static final String TEST_ROLE_CODE_01 = "ROLE_1211"; private static final String TEST_ROLE_CODE_02 = "ROLE_1211_1"; @Override @Hook(module = {"base"}, model = {ViewAction.MODEL_MODEL}, fun = {"homepage"}) public Object run(Function function, Object ret) { if (ret == null) { return null; } ViewAction viewAction = getViewActionByCurrentRole(); if (viewAction != null) { ViewAction retNew = CommonApiFactory.getApi(PageLoadAction.class).load(viewAction); ViewAction viewActionRet = (ViewAction) ((Object[]) ret)[0]; viewActionRet.set_d(retNew.get_d()); } return ret; } protected ViewAction getViewActionByCurrentRole() { try { PamirsUser user = new PamirsUser(); user.setId(PamirsSession.getUserId()); user.fieldQuery(PamirsUser::getRoles); List<AuthRole> roles = user.getRoles(); if (CollectionUtils.isNotEmpty(roles)) { List<String> roleCodes = roles.stream().map(AuthRole::getCode).collect(Collectors.toList()); if (roleCodes.contains(TEST_ROLE_CODE_01)) { return new ViewAction().setModel(DemoItemCategory.MODEL_MODEL).setName("DemoMenus_ItemPMenu_DemoItemAndCateMenu_DemoItemCategoryMenu").queryOne(); } else if (roleCodes.contains(TEST_ROLE_CODE_02)) { return new ViewAction().setModel(DemoItemLabel.MODEL_MODEL).setName("DemoMenus_ItemPMenu_DemoItemAndCateMenu_DemoItemLabelMenu").queryOne(); } } } catch (PamirsException exception) { if (PamirsSession.getUserId() == null) { throw PamirsException.construct(BaseExpEnumerate.BASE_USER_NOT_LOGIN_ERROR, exception.getCause()).errThrow(); } else { throw exception; } } return null; } }

    2024年7月6日
    2.5K00
  • JSON转换工具类

    JSON转换工具类 JSON转对象 pro.shushi.pamirs.meta.util.JsonUtils JSON转模型 pro.shushi.pamirs.framework.orm.json.PamirsDataUtils

    2023年11月1日
    1.9K00
  • 环境运行时Jar版本控制

    环境运行时Jar版本控制 前景 为了避免基于低代码定义产生的元数据错乱。因此产生了运行时Jar版本检查功能。 现象 如果当前运行时依赖的Ja版本低于已安装版本,启动时会有如下类似信息提示: 解决 按照提示升级依赖Jar版本 通过启动参数 -PgoBack=true 强制覆盖安装当前运行时版本 java -jar 方式 java -jar xxx.jar -PgoBack=true [其他参数] mvn spring-boot run 方式 mvn clean compile spring-boot:run -Dspring-boot.run.arguments=”-PgoBack=true [其他参数]”

    2025年3月10日
    1.1K00

Leave a Reply

登录后才能评论