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

相关推荐

  • JSON转换工具类

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

    2023年11月1日
    2.1K00
  • action 和 function 有什么区别

    在 Oinone(开源低代码 / 企业应用开发平台) 里,Action 和 Function 都是“可被调用的逻辑单元”,但它们的定位和使用场景不同。可以简单理解为: Function = 纯逻辑函数(偏后端能力) Action = 面向业务操作的动作(偏应用行为 / UI触发) 下面给你详细对比一下。 1️⃣ Function:函数(逻辑能力) Function 更像是一个 可复用的服务方法。 特点 通常是 纯逻辑处理 不直接绑定 UI 可以被 Action / Service / 其他 Function 调用 用来封装 业务计算或工具逻辑 常见用途 比如: 价格计算 数据校验 数据转换 调用第三方 API 复杂业务规则 示例 @Function(openLevel = FunctionOpenEnum.API) @Function.Advanced(type = FunctionTypeEnum.QUERY) public TradeOrder computePrice(TradeOrder data) { return data; } 用途: 订单金额计算逻辑 然后可能被多个地方调用: Action -> 调用 Function Service -> 调用 Function Workflow -> 调用 Function 📌 核心:可复用业务逻辑 2️⃣ Action:动作(业务操作) Action 是一个 业务动作,通常是 用户触发的行为。 特点 通常绑定 UI 可以在 按钮 / 菜单 / API / 工作流 中触发 通常操作 模型数据 可以调用 Function 常见用途 例如: 创建订单 提交审批 发布文章 批量删除 导入数据 示例 @Action public void submitOrder(Order order){ order.setStatus("SUBMITTED"); } UI 可能是: 订单详情页 [提交订单] 按钮 点击按钮 → 调用 Action。 📌 核心:业务行为入口 3️⃣ 核心区别总结 维度 Action Function 定位 业务动作 逻辑函数 是否绑定 UI 通常是 否 是否直接给用户操作 是 否 是否可复用 一般 很高 是否操作模型 常见 不一定 调用关系 可调用 Function 不调用 Action 4️⃣ 调用关系(典型架构) 通常推荐的结构: UI按钮 ↓ Action(业务入口) ↓ Function(业务逻辑) ↓ DAO / Repository 例如: 提交订单按钮 ↓ submitOrderAction ↓ checkInventoryFunction calcPriceFunction createOrderFunction 这样: Action 只负责 流程 Function 负责 逻辑 代码会更清晰。 5️⃣…

    2026年3月12日
    28700
  • 如何把平台功能菜单加到项目中?

    概述 在使用Oinone低代码平台进行项目开发的过程中,会存在把平台默认提供的菜单加到自己的项目中。这篇文章介绍实现方式,以角色管理为例。 1. 低代码情况 即项目是通过后端代码初始化菜单的方式。 通常在 XXXMenu.java类通过@UxMenu注解的方式,代码片段如下: 此种情况与添加项目本地的菜单无区别,具体代码: @UxMenu(value = "账号管理", icon = "icon-yonghuguanli") class AccountManage { @UxMenu("用户管理") @UxRoute(value = CustomerCompanyUserProxy.MODEL_MODEL, title = "用户管理") class CompanyUserManage { } @UxMenu("角色管理") @UxRoute(value = AuthRole.MODEL_MODEL, module = AdminModule.MODULE_MODULE, title = "角色管理") class RoleManage { } @UxMenu("操作日志") @UxRoute(value = OperationLog.MODEL_MODEL, module = AdminModule.MODULE_MODULE/***菜单所挂载的模块**/) class OperateLog { } } 2. 无代码情况 在界面设计器中,新建菜单–>选择绑定已有页面,进行发布即可。

    2024年4月19日
    1.8K00
  • 【后端】项目开发后端知识要点地图

    大类 明细 文档链接 平台基础 如何开发Action,理解前后端协议 如何开发Action,理解前后端协议 CDN配置及文件操作相关 OSS(CDN)配置和文件系统的一些操作 MINIO无公网访问地址下OSS的配置 MINIO无公网访问地址下OSS的配置 分库分表与自定义分表规则 分库分表与自定义分表规则 Oinone引入搜索引擎(增强模型) Oinone引入搜索引擎(增强模型) 引入搜索/增强模型Channel)常见问题解决办法 引入搜索(增强模型Channel)常见问题解决办法 框架之MessageHub(信息提示) 框架之MessageHub(信息提示) DsHint和BatchSizeHint的使用 DsHint(指定数据源)和BatchSizeHint(查询批次数量) Oinone连接外部数据源方案 Oinone连接外部数据源方案 如何自定义SQL(Mapper)语句 如何自定义SQL(Mapper)语句 IWrapper、QueryWrapper和LambdaQueryWrapper使用 IWrapper、QueryWrapper和LambdaQueryWrapper使用 如何在代码中使用自增ID、手动方式获取CODE 如何在代码中使用自增ID、手动方式获取CODE 函数之触发与定时配置和示例 函数之触发与定时配置和示例 函数之异步执行 函数之异步执行 查询时自定义排序字段和排序规则 查询时自定义排序字段和排序规则 非存储字段搜索 非存储字段搜索,适应灵活的搜索场景 枚举/二进制枚举/多值枚举 如何使用位运算的数据字典 全局首页及应用首页配置方法(homepage) 全局首页及应用首页配置方法(homepage) 缓存连接由Jedis切换为Lettuce 缓存连接由Jedis切换为Lettuce GraphQL请求:后端接口实现逻辑解析 GraphQL请求:后端接口实现逻辑解析 Nacos支持 Nacos作为注册中心 Oinone项目引入Nacos作为注册中心 Nacos作为配置中心 Oinone项目引入Nacos作为配置中心 Nacos做为注册中心调用SpringCloud服务 Nacos做为注册中心调用SpringCloud服务 分布式相关 如何构建分布式项目 Oinone如何支持构建分布式项目 构建分布式项目一些要点(dubbo日志关闭等) Oinone构建分布式项目一些注意点 信创支持 后端部署使用达梦数据库 【达梦】后端使用达梦数据库 后端部署使用PostgreSQL数据库 【PostgreSQL】后端使用PostgreSQL数据库 后端部署使用OpenGauss数据库 【OpenGauss】后端使用OpenGauss数据库 后端部署使用MSSQL数据库(SQLServer) 【MSSQL】后端部署使用MSSQL数据库(SQLServer) 东方通Web和Tomcat部署Oinone项目 东方通Web和Tomcat部署Oinone项目 常见扩展 如何增加用户中心的菜单 如何增加用户中心的菜单 导入导出 如何批量导入 如何批量导入 如何支持多Excel多个Sheet导入功能 如何支持多Excel多个Sheet导入功能 如何自定义Excel导入功能 如何自定义Excel导入功能 如何自定义Excel导出功能 如何自定义Excel导出功能 如何自定义表达式 如何自定义表达式 登录扩展 对接外部SSO Oinone登录扩展:对接SSO(4.7.8及之后的版本) 自定义占位符 自定义RSQL占位符及在权限中使用 自定义RSQL占位符(placeholder)及在权限中使用 自定义数据权限拦截处理 自定义数据权限拦截处理 设计器公共 后端无代码设计器Jar包启动方法 后端无代码设计器Jar包启动方法 界面设计器 页面跳转时增加跳转参数 页面跳转时增加跳转参数 界面设计器的导入导出 界面设计器的导入导出 流程设计器 项目中工作流引入和流程触发 项目中工作流引入和流程触发 流程扩展自定义函数示例代码汇总 工作流-流程扩展自定义函数示例代码汇总 工作流-流程代办等页面自定义 工作流-流程代办等页面自定义 审核撤回/回退/拒绝钩子使用 工作流审核撤回/回退/拒绝钩子使用 流程设计器的导入导出 流程设计器的导入导出 如何添加工作流运行时依赖 如何添加工作流运行时依赖 数据可视化 项目中图表设计器引入 数据可视化-项目中数据可视化的实现引入 自定义图表模版 数据可视化中图表的低无一体 图表设计器数据获取示例 数据可视化-数据可视化数据获取示例 如何添加数据可视化运行时依赖 如何添加数据可视化运行时依赖 图表设计器的设计数据导入导出 图表设计器的设计数据导入导出

    2024年5月21日
    2.5K00
  • 自定义数据权限拦截处理

    业务场景 公司给员工对哪些模块有访问权限,这个时候就需要在员工访问模块表的时候做数据过滤, 解决方案 我们可以通过平台提供的数据过滤占位符解决这个问题,新建一条数据行权限,过滤语句条件是占位符,再编写占位符的解析逻辑 1.初始化权限基础数据 package pro.shushi.pamirs.demo.core.init; import com.google.common.collect.Lists; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.constants.AuthConstants; import pro.shushi.pamirs.auth.api.enmu.AuthGroupTypeEnum; import pro.shushi.pamirs.auth.api.enmu.PermissionDataSourceEnum; import pro.shushi.pamirs.auth.api.enmu.PermissionTypeEnum; import pro.shushi.pamirs.auth.api.model.AuthGroup; import pro.shushi.pamirs.auth.api.model.AuthRole; import pro.shushi.pamirs.auth.api.model.ResourcePermission; import pro.shushi.pamirs.boot.base.model.UeModule; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.api.init.InstallDataInit; import pro.shushi.pamirs.boot.common.api.init.UpgradeDataInit; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.placeholder.EmployeeModulePlaceholder; import pro.shushi.pamirs.framework.common.utils.ObjectUtils; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.domain.module.ModuleDefinition; import java.util.Collections; import java.util.List; @Slf4j @Component @Order(0) public class DemoModuleBizInit implements InstallDataInit, UpgradeDataInit { @Override public List<String> modules() { return Collections.singletonList(DemoModule.MODULE_MODULE); } @Override public int priority() { return 0; } @Override public boolean init(AppLifecycleCommand command, String version) { this.initAuth(); return true; } @Override public boolean upgrade(AppLifecycleCommand command, String version, String existVersion) { this.initAuth(); return true; } private void initAuth() { AuthGroup authGroup = new AuthGroup(); authGroup.setName("测试权限组") .setDisplayName("测试权限组") .setType(AuthGroupTypeEnum.RUNTIME) .setActive(true); authGroup.createOrUpdate(); AuthRole authRole = new AuthRole(); authRole.setCode("TEST_ROLE_1") .setName("测试角色") .setRoleTypeCode(AuthConstants.ROLE_SYSTEM_TYPE_CODE) .setPermissionDataSource(PermissionDataSourceEnum.CUSTOM) .setActive(true); authRole.createOrUpdate(); authRole.setGroups(Lists.newArrayList(authGroup)); authRole.fieldSave(AuthRole::getGroups); ResourcePermission authPermission = new ResourcePermission(); authPermission.setName("测试模块权限过滤") .setDomainExp(EmployeeModulePlaceholder.PLACEHOLDER) .setModel(ModuleDefinition.MODEL_MODEL) .setPermRead(true) .setPermRun(true) .setPermissionType(PermissionTypeEnum.ROW) .setPermissionDataSource(PermissionDataSourceEnum.CUSTOM) .setCanShow(true) .setActive(true); ResourcePermission authPermission2 = ObjectUtils.clone(authPermission); authPermission2.setName("测试ue模块权限过滤").setModel(UeModule.MODEL_MODEL); authGroup.setPermissions(Lists.newArrayList(authPermission, authPermission2)); authGroup.fieldSave(AuthGroup::getPermissions); } } 这里演示的module表比较特殊,需要同时设置ModuleDefinition和UeModule这2个模型做数据过滤 2.编写占位符拦截替换逻辑 package pro.shushi.pamirs.demo.core.placeholder; import org.springframework.stereotype.Component; import pro.shushi.pamirs.user.api.AbstractPlaceHolderParser; @Component public class EmployeeModulePlaceholder extends AbstractPlaceHolderParser { public static final String PLACEHOLDER = "${employeeModulePlaceholder}"; protected String value() { // TODO…

    2023年11月24日
    1.2K00

Leave a Reply

登录后才能评论