自定义RSQL占位符(placeholder)及在权限中使用

1 自定义RSQL占位符常用场景

  • 统一的数据权限配置
  • 查询表达式的上下文变量扩展

2 自定义RSQL的模板

/**
 * 演示Placeholder占位符基本定义
 *
 * @author Adamancy Zhang at 13:53 on 2024-03-24
 */
@Component
public class DemoPlaceHolder extends AbstractPlaceHolderParser {

    private static final String PLACEHOLDER_KEY = "${thisPlaceholder}";

    /**
     * 占位符
     *
     * @return placeholder
     */
    @Override
    public String namespace() {
        return PLACEHOLDER_KEY;
    }

    /**
     * 占位符替换值
     *
     * @return the placeholder replace to the value
     */
    @Override
    protected String value() {
        return PamirsSession.getUserId().toString();
    }

    /**
     * 优先级
     *
     * @return execution order of placeholders, ascending order.
     */
    @Override
    public Integer priority() {
        return 0;
    }

    /**
     * 是否激活
     *
     * @return the placeholder is activated
     */
    @Override
    public Boolean active() {
        return true;
    }
}

注意事项

  • 在一些旧版本中,priorityactive可能不起作用,为保证升级时不受影响,请保证该属性配置正确。

  • PLACEHOLDER_KEY变量表示自定义占位符使用的关键字,需按照所需业务场景的具体功能并根据上下文语义正确定义。

  • 为保证占位符可以被正确替换并执行,所有占位符都不应该出现重复,尤其是不能与系统内置的重复。

3 占位符使用时的优先级问题

多个占位符在进行替换时,会根据优先级按升序顺序执行,如需要指定替换顺序,可使用SpringOrder注解对其进行排序。

import org.springframework.core.annotation.Order;

@Order(0)

4 Oinone平台内置的占位符

占位符 数据类型 含义 备注
${currentUser} String 当前用户ID 未登录时无法使用
${currentRoles} Set<String> 当前用户的角色ID集合 未登录时无法使用

5 如何覆盖平台内置的占位符?

通过指定占位符的优先级,并定义相同的namespace可优先替换。

6 如何定义会话级别的上下文变量?

在上述模板中,我们使用的是Oinone平台内置的上下文变量进行演示,通常情况下,我们需要根据实际业务场景增加上下文变量,以此来实现所需功能。

下面,我们将根据当前用户获取当前员工ID定义该上下文变量进行演示。

/**
 * 员工Session
 *
 * @author Adamancy Zhang at 14:33 on 2024-03-24
 */
@Component
public class EmployeeSession implements HookBefore {

    private static final String SESSION_KEY = "CUSTOM_EMPLOYEE_ID";

    @Autowired
    private DemoEmployeeService demoEmployeeService;

    public static String getEmployeeId() {
        return PamirsSession.getTransmittableExtend().get(SESSION_KEY);
    }

    @Override
    @Hook(priority = 1)
    public Object run(Function function, Object... args) {
        Long userId = PamirsSession.getUserId();
        if (userId == null) {
            return function;
        }
        if (StringUtils.isBlank(EmployeeSession.getEmployeeId())) {
            PamirsSession.getTransmittableExtend().put(SESSION_KEY, getCurrentEmployeeId());
        }
        return function;
    }

    private String getCurrentEmployeeId() {
        String employeeId = getEmployeeIdByCache();
        if (employeeId == null) {
            employeeId = demoEmployeeService.getCurrentEmployeeId();
        }
        return employeeId;
    }

    private String getEmployeeIdByCache() {
        // do something.
        return null;
    }
}

注意事项

  • 使用HookBefore在请求发起时向上下文中设置employeeId,使用EmployeeSession.getEmployeeId()获取即可。

  • HookBefore的优先级设置为priority = 1,需要该Hook在平台内置的UserHook之后执行,以确保PamirsSession.getUserId()中的值已经被正确设置。

  • DemoEmployeeService服务应使用平台@Fun@Function注解进行实现,以确保该Session可在分布式环境中正确运行。

  • getEmployeeIdByCache方法需自行实现,在运行时使用缓存可有效提高性能。

7 将员工Sessionplaceholder中使用

DemoPlaceHolder改写,使用${currentEmployeeId}获取员工Session中保存的employeeId

/**
 * 演示Placeholder占位符使用员工Session
 *
 * @author Adamancy Zhang at 15:02 on 2024-03-24
 */
@Component
public class DemoPlaceHolder extends AbstractPlaceHolderParser {

    private static final String PLACEHOLDER_KEY = "${currentEmployeeId}";

    /**
     * 占位符
     *
     * @return placeholder
     */
    @Override
    public String namespace() {
        return PLACEHOLDER_KEY;
    }

    /**
     * 占位符替换值
     *
     * @return the placeholder replace to the value
     */
    @Override
    protected String value() {
        return EmployeeSession.getEmployeeId();
    }

    /**
     * 优先级
     *
     * @return execution order of placeholders, ascending order.
     */
    @Override
    public Integer priority() {
        return 0;
    }

    /**
     * 是否激活
     *
     * @return the placeholder is activated
     */
    @Override
    public Boolean active() {
        return true;
    }
}

至此,我们已完成了一个员工ID占位符。

8 在权限配置时使用该占位符作为过滤条件

下面,我们将模拟一个简单业务场景,详细介绍该占位符如何在业务中使用。

8.1 场景描述

当前系统中包含部门员工两个模型,模型的基本定义如下所示:

部门

/**
 * 演示部门
 *
 * @author Adamancy Zhang at 15:18 on 2024-03-24
 */
@Model.model(DemoDepartment.MODEL_MODEL)
@Model(displayName = "演示部门", labelFields = "name")
public class DemoDepartment extends IdModel {

    private static final long serialVersionUID = -300189841334506668L;

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

    @Field(displayName = "部门名称")
    private String name;

    @Field(displayName = "管理员")
    private DemoEmployee manager;

}

员工

/**
 * 演示员工
 *
 * @author Adamancy Zhang at 15:17 on 2024-03-24
 */
@Model.model(DemoEmployee.MODEL_MODEL)
@Model.Advanced(unique = {"bindingUserId"})
@Model(displayName = "演示员工", labelFields = "name")
public class DemoEmployee extends IdModel {

    private static final long serialVersionUID = -6237083162460091500L;

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

    @Field(displayName = "员工名称")
    private String name;

    @Field.Relation(relationFields = {"bindingUserId"}, referenceFields = {"id"})
    @Field(displayName = "绑定用户")
    private PamirsUser bindingUser;

    @Field(displayName = "绑定用户ID")
    private Long bindingUserId;

}

我们要求当前登录用户仅能查看其作为管理员的所在部门,因此,我们需要使用managerId == ${currentEmployeeId}这一过滤条件,在权限中进行配置并使其生效。

此处忽略数据准备过程,仅展示关键页面的配置及最终效果。

8.2 权限项配置

从应用中切换至权限模块,并选择权限项列表,创建一个数据权限项,并按照下图内容进行配置。

权限项配置

8.3 角色权限配置

选择角色列表,并选择角色表格中指定角色的权限配置按钮,进入权限配置页面,并按照下图内容进行配置。

PS:这里省略了动作权限相关配置,配置的权限应保证该角色可以正确进入演示部门页面查看效果。

角色权限配置

8.4 为用户绑定指定角色(如已绑定可忽略该步骤)

从应用中切换至用户中心模块,选择指定用户,绑定指定角色。

8.5 在演示部门页面查看权限配置效果

未配置权限页面效果

未配置权限页面效果

权限配置后页面效果

权限配置后页面效果

本文来自投稿,不代表Oinone社区立场,如若转载,请注明出处:https://doc.oinone.top/backend/6393.html

(1)
张博昊的头像张博昊数式管理员
上一篇 2024年3月20日 pm3:03
下一篇 2024年3月24日 pm1:45

相关推荐

  • 国际化-语言和时区设置

    国际化-翻译 1、引入翻译的包 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-translate</artifactId> </dependency> 2、默认逻辑:在系统的右上角,切换【系统语言后】,用户所选择的语言会保存到对应的用户信息中,后续所有的请求都会拿这个「语言」的值,并将其放入到PamirsSession#Lang中。3、实际项目可以通过自定义Session逻辑,根据实际业务覆盖掉默认方式,并将其设置在PamirsSession中: PamirsSession.setLang(langCode); 构建自定义Session参考:https://shushi.yuque.com/yoxz76/oio3/kg2sgr 构建自定义Session的逻辑中,根据业务逻辑把获取到的langCode设置都PamirsSession 4、目标语言编码说明语言编码必须符合ISO标准,即语言ISO代码。国际化-语言代码表-Language Codes参考下面的链接:https://blog.csdn.net/qq827245563/article/details/131552695 国际化-时区 1、引入时区的包 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-timezone</artifactId> </dependency> 2、时区设置类似语言(langCode)3、在自定义Session(与设置语言共同的Session自定义)中,根据实际业务覆盖掉默认方式,并将其设置在TimezoneSession中: pro.shushi.pamirs.timezone.session.TimezoneSession#setTimezone(TimeZone timezone) 其他说明 PamirsSession 和 TimezoneSession 都是请求级别的;即每次请求都会自动销毁; 因此在自定义Session中覆盖这两个属性的默认值的时候特别注意一下性能。

    2023年12月4日
    1.2K00
  • 如何扩展自有的文件存储系统

    介绍 数式Oinone默认提供了阿里云、腾讯云、华为云、又拍云、Minio和本地文件存储这几种文件存储系统,如果我们有其他的文件存储系统需要对接,或者是扩展现有的文件系统,可以通过SPI继承AbstractFileClient注册新的文件存储系统。 代码示例 这里以扩展自有的本地文件系统为例 继承了内置的本地文件存储LocalFileClient,将其中上传文件的方法重写 package pro.shushi.pamirs.demo.core.file; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; import pro.shushi.pamirs.framework.connectors.cdn.client.LocalFileClient; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.common.spi.SPI; import javax.servlet.http.HttpServletRequest; @Slf4j @Component // 注册新的文件存储系统类型 @SPI.Service(DemoLocalFileClient.TYPE) @RestController @RequestMapping("/demo_file") public class DemoLocalFileClient extends LocalFileClient { public static final String TYPE = "DEMO_LOCAL"; @Override public CdnFileForm getFormData(String fileName) { CdnConfig cdnConfig = getCdnConfig(); CdnFileForm fileForm = new CdnFileForm(); String uniqueFileName = Spider.getDefaultExtension(CdnFileNameApi.class).getNewFilename(fileName); String fileKey = getFileKey(cdnConfig.getMainDir(), uniqueFileName); //前端获取uploadUrl,上传文件到该地址 fileForm.setUploadUrl(cdnConfig.getUploadUrl() + "/demo_file/upload"); //上传后,前端将downloadUrl返回给后端 fileForm.setDownloadUrl(getDownloadUrl(fileKey)); fileForm.setFileName(uniqueFileName); Map<String, Object> formDataJson = new HashMap<>(); formDataJson.put("uniqueFileName", uniqueFileName); formDataJson.put("key", fileKey); fileForm.setFormDataJson(JSON.toJSONString(formDataJson)); return fileForm; } @ResponseBody @RequestMapping(value = "/upload", produces = "multipart/form-data;charset=UTF-8",method = RequestMethod.POST) public String uploadFileToLocal(HttpServletRequest request) { MultipartFile file = ((StandardMultipartHttpServletRequest) request).getFile("file"); // 例如可以根据file文件类型判断哪些文件是否可以上传 return super.uploadFileToLocal(request); } } 在application.yml内配置 cdn: oss: name: 本地文件系统 # 这里的type与代码中定义的文件存储系统类型对应 type: DEMO_LOCAL bucket: pamirs uploadUrl: http://127.0.0.1:8190 downloadUrl: http://127.0.0.1:6800 validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: /Users/demo/workspace/static

    2024年10月24日
    79700
  • Oinone环境保护(v5.2.3以上)

    概述 Oinone平台为合作伙伴提供了环境保护功能,以确保在一套环境可以在较为安全前提下修改配置文件,启动多个JVM等部署操作。 本章内容主要介绍与环境保护功能相关的启动参数。 名词解释 本地开发环境:开发人员在本地启动业务工程的环境 公共环境:包含设计器镜像和业务工程的环境 环境保护参数介绍 【注意】参数是加在程序实参 (Program arguments)上,通常可能错误的加在Active Profiles上了 -PenvProtected=${value} 是否启用环境保护,默认为true。 环境保护是通过与最近一次保存在数据库的base_platform_environment表中数据进行比对,并根据每个参数的配置特性进行判断,在启动时将有错误的内容打印在启动日志中,以便于开发者进行问题排查。 除此之外,环境保护功能还提供了一些生产配置的优化建议,开发者可以在启动时关注这些日志,从而对生产环境的配置进行调优。 -PsaveEnvironments=${value} 是否将此次启动的环境参数保存到数据库,默认为true。 在某些特殊情况下,为了避免公共环境中的保护参数发生不必要的变化,我们可以选择不保存此次启动时的配置参数到数据库中,这样就不会影响其他JVM启动时发生校验失败而无法启动的问题。 -PstrictProtected=${value} 是否使用严格校验模式,默认为false 通常我们建议在公共环境启用严格校验模式,这样可以最大程度的保护公共环境的元数据不受其他环境干扰。 PS:在启用严格校验模式时,需避免内外网使用不同连接地址的场景。如无法避免,则无法启用严格校验模式。 常见问题 需要迁移数据库,并更换了数据库连接地址该如何操作? 将原有数据库迁移到新数据库。 修改配置文件中数据库的连接地址。 在启动脚本中增加-PenvProtected=false关闭环境保护。 启动JVM服务可以看到有错误的日志提示,但不会中断本次启动。 移除启动脚本中的-PenvProtected=false或将值改为true,下次启动时将继续进行环境保护检查。 可查看数据库中base_platform_environment表中对应数据库连接配置已发生修改,此时若其他JVM在启动前未正确修改,则无法启动。 本地开发时需要修改Redis连接地址到本地,但希望不影响公共环境的使用该如何操作? PS:由于Redis中的元数据缓存是根据数据库差量进行同步的,此操作会导致公共环境在启动时无法正确刷新Redis中的元数据缓存,需要配合pamirs.distribution.session.allMetaRefresh参数进行操作。如无特殊必要,我们不建议使用该形式进行协同开发,多次修改配置会导致出错的概率增加。 本地环境首次启动时,除了修改Redis相关配置外,还需要配置pamirs.distribution.session.allMetaRefresh=true,将本地新连接的Redis进行初始化。 在本地启动时,增加-PenvProtected=false -PsaveEnvironments=false启动参数,以确保本地启动不会修改公共环境的配置,并且可以正常通过环境保护检测。 本地环境成功启动并正常开发功能后,需要发布到公共环境进行测试时,需要先修改公共环境中业务工程配置pamirs.distribution.session.allMetaRefresh=true后,再启动业务工程。 启动一次业务工程后,将配置还原为pamirs.distribution.session.allMetaRefresh=false。

    2024年10月21日
    1.3K00
  • Oinone协同开发源码分析

    前提 源码分析版本是 5.1.x版本 什么是协同开发模式 协同开发模式解决的是不同开发,在开发同一个模型时,不会相互影响,也不会影响到测试环境详见:Oinone协同开发使用手册 协同开发原理 在协同模式下,本地开发的元数据,配置pamirs.data.distribution.session.ownSign参数后,元数据前缀加ownSign值,然后只存在redis缓存,不落库。其它环境无法直接访问到该数据。测试环境,或其它环境访问,需要在url上加ownSign等于设置的,则读redis数据时,除了加载通用数据,也会合并ownSign前缀的redis数据,显示出来 注意事项 协同开发仅支持界面设计器,其他设计器均不支持 不支持权限配置 不支持工作流触发 版本支持 完整支持5.1.0及以上 功能详解 启动时操作 做元数据保护检查 配置ownSign,则key拼接为 ownSign + ‘:’ + key 清除掉ownSign的redis缓存数据;非ownSign不用清理 计算差量数据 有差量数据,放入ownSign标识数据,并清理本地标识 dubbo注册服务,group拼接group + ownSign 后进行注册 读取时操作 读本地 组装key: ownSign + ‘:’ + key 本地缓存有数据,更新缓存本地数据,返回 本地没有数据,读redis,并插入本地缓存 读远程 dubbo注册消费者,group拼接group + ownSign 后进行泛化调用 元数据保护检查 开启数据保护模式,在启动参数里加-PmetaProtected=pamirs 会在启动时,往redis里写入数据 private static final String META_PROTECTED_KEY = “pamirs:check:meta-protected”; private void writeMetaProtected(String metaProtected) { stringRedisTemplate.opsForValue().set(META_PROTECTED_KEY, metaProtected); } 如果同时又设置 pamirs.data.distribution.session.ownSign则会报错 在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign] 处理逻辑如下 看redis是否启用保护标识的值 获取pamirs.distribution.session.ownSign配置 没有启动参数 且redis没有值,则retrun 如果有启动参数且配置了ownSign,报错 在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign] 如果有启动参数且 redis没有值或启动参数设置 -P metaForceProtected,则写入redis 如果有启动参数, 且启动参数跟redis值不同,则报错[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]] 如果没有启动参数且redis有值,但没有配置ownSign 报错[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]] 核心代码如下MetadataProtectedChecker public void process(AppLifecycleCommand command, Set<String> runModules, List<ModuleDefinition> installModules, List<ModuleDefinition> upgradeModules, List<ModuleDefinition> reloadModules) { String currentMetaProtected = stringRedisTemplate.opsForValue().get(META_PROTECTED_KEY); String metaProtected = getMetaProtected(); boolean hasCurrentMetaProtected = StringUtils.isNotBlank(currentMetaProtected); boolean hasMetaProtected = StringUtils.isNotBlank(metaProtected); if (!hasCurrentMetaProtected && !hasMetaProtected) { return; } if (hasMetaProtected) { if (Spider.getDefaultExtension(SessionFillOwnSignApi.class).handleOwnSign()) { // 如果有启动参数且配置了ownSign throw new UnsupportedOperationException(“在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign]”); } if (!hasCurrentMetaProtected || isForceProtected()) { writeMetaProtected(metaProtected); } else if (!metaProtected.equals(currentMetaProtected)) { // 如果有启动参数, 且启动参数跟redis值不同 throw unsupportedLocalOperation(); } } else { if (Spider.getDefaultExtension(SessionFillOwnSignApi.class).handleOwnSign()) { return; } // 没有启动参数且redis有值,但没有配置ownSign 报错 throw unsupportedLocalOperation(); } } 取ownSign方式 看header是否有ownSign这个标识 header没有,则从配置里取,并放到header里 ownSign的获取核心代码 CdDistributionSessionFillOwnSignApi @Override public String getCdOwnSign() { String cdOwnSign = null; // 看header是否有ownSign这个标识…

    2024年9月12日
    1.6K00
  • 【后端】项目开发后端知识要点地图

    目录 工程结构篇 协议篇 GraphQL请求:后端接口实现逻辑解析 基本功能及配置篇 Dubbo Dubbo配置详解 Nacos Oinone项目引入Nacos作为注册中心 Oinone项目引入Nacos作为配置中心 Nacos做为注册中心调用其他系统的SpringCloud服务 OSS OSS(CDN)配置和文件系统的一些操作 MINIO无公网访问地址下OSS的配置 Trigger/Async/Schedule 函数之触发与定时配置和示例 函数之异步执行 Excel导入/导出(file) Excel批量导入 【Excel导入/导出】多Sheet导入导出示例 如何自定义Excel导入功能 如何自定义Excel导出功能 Excel导入导出模板翻译 Expression(表达式) 扩展内置函数表达式 ShardingJDBC(分库分表) 分库分表与自定义分表规则 Elasticsearch(ES) Oinone引入搜索引擎(增强模型) 引入搜索(增强模型Channel)常见问题解决办法 数据库方言配置(Dialect) 【DM】后端部署使用Dameng数据库(达梦) 【PostgreSQL】后端部署使用PostgreSQL数据库(PGSQL) 【OpenGauss】后端部署使用OpenGauss数据库(高斯) 【MSSQL】后端部署使用MSSQL数据库(SQLServer) 【KDB】后端部署使用Kingbase数据库(人大金仓/电科金仓) 【Oracle】后端部署使用Oracle数据库 【OceanBase】后端部署使用 OceanBase 数据库(海扬/OB) 其他功能使用文档 框架之MessageHub(信息提示) DsHint(指定数据源)和BatchSizeHint(指定批次数量) IWrapper、QueryWrapper和LambdaQueryWrapper使用 查询时自定义排序字段和排序规则 如何在代码中使用自增ID和获取序列 非存储字段搜索,适应灵活的搜索场景 如何使用位运算的数据字典 全局首页及应用首页配置方法(homepage) 如何增加用户中心的菜单 自定义RSQL占位符(placeholder)及在权限中使用 Function、Action函数使用规范 特定场景解决方案 Oinone连接外部数据源方案 如何自定义SQL(Mapper)语句 工程部署 后端部署 Oinone平台部署及依赖说明 v4.7 v5.0 v5.1 v5.3 v6.2 Oinone License 许可证使用常见问题 后端无代码设计器Jar包启动方法 Oinone环境保护(v5.2.3以上) 设计器部署 Oinone设计器部署参数说明 Oinone离线部署设计器镜像 Oinone离线部署设计器JAR包 Docker部署常见问题 其他环境部署 东方通Web和Tomcat部署Oinone项目 可视化调试工具 Oinone平台可视化调试工具 协同开发 Oinone协同开发使用手册 工作流 项目中工作流引入和流程触发 【工作流】流程扩展自定义函数示例代码汇总 工作流-流程代办等页面自定义 工作流审核撤回/回退/拒绝钩子使用 如何添加工作流运行时依赖(前后端) 数据可视化运行时 如何添加数据可视化运行时依赖 界面设计器 如何实现页面间的跳转 界面设计器的导入导出 流程设计器 流程设计器的导入导出 数据可视化 数据可视化-项目中如何引用图表、报表、大屏 数据可视化中图表的低无一体 数据可视化-如何自定义查询数据方法 数据可视化的导入导出 其他 EIP开放接口使用MD5验签发起请求(v5.x) 缓存连接由Jedis切换为Lettuce Oinone登录扩展:对接SSO QA 导入设计数据时dubbo超时导入失败

    2024年10月23日
    3.3K00

Leave a Reply

登录后才能评论