自定义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

Like (1)
张博昊's avatar张博昊数式管理员
Previous 2024年3月20日 pm3:03
Next 2024年3月24日 pm1:45

相关推荐

  • EIP开放接口使用MD5验签发起请求(v5.x)

    验签工具类 PS:该验签方法仅在pamirs-core的5.0.16版本以上可正常使用 public class EipSignUtils { public static final String SIGN_METHOD_MD5 = "md5"; private static final String SIGN_METHOD_HMAC = "hmac"; private static final String SECRET_KEY_ALGORITHM = "HmacMD5"; private static final String MESSAGE_DIGEST_MD5 = "MD5"; public static String signTopRequest(Map<String, String> params, String secret, String signMethod) throws IOException { // 第一步:检查参数是否已经排序 String[] keys = params.keySet().toArray(new String[0]); Arrays.sort(keys); // 第二步:把所有参数名和参数值串在一起 StringBuilder query = new StringBuilder(); if (SIGN_METHOD_MD5.equals(signMethod)) { query.append(secret); } for (String key : keys) { String value = params.get(key); if (StringUtils.isNoneBlank(key, value)) { query.append(key).append(value); } } // 第三步:使用MD5/HMAC加密 byte[] bytes; if (SIGN_METHOD_HMAC.equals(signMethod)) { bytes = encryptHMAC(query.toString(), secret); } else { query.append(secret); bytes = encryptMD5(query.toString()); } // 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用) return byte2hex(bytes); } private static byte[] encryptHMAC(String data, String secret) throws IOException { byte[] bytes; try { SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), SECRET_KEY_ALGORITHM); Mac mac = Mac.getInstance(secretKey.getAlgorithm()); mac.init(secretKey); bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); } catch (GeneralSecurityException e) { throw new IOException(e.toString(), e); } return bytes; } private static byte[] encryptMD5(String data) throws IOException { return encryptMD5(data.getBytes(StandardCharsets.UTF_8)); } private static byte[] encryptMD5(byte[] data) throws IOException { try { MessageDigest md = MessageDigest.getInstance(MESSAGE_DIGEST_MD5); return md.digest(data); } catch (NoSuchAlgorithmException e)…

    2024年6月29日
    1.8K00
  • 流程设计流程结束通知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.6K00
  • 工作流引入流程概览与流程监控

    流程概览依赖说明 使用 流程概览 功能前,需要在项目中引入 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日
    51000
  • Oinone平台可视化调试工具

    为方便开发者定位问题,我们提供了可视化的调试工具。
    该文档将介绍可视化调试工具的基本使用方法。

    2024年4月13日
    1.6K00
  • 缓存连接由Jedis切换为Lettuce

    Jedis和Lettuce的区别 Jedis是同步的,不支持异步,Jedis客户端实例不是线程安全的,需要每个线程一个Jedis实例,所以一般通过连接池来使用Jedis; Lettuce是基于Netty框架的事件驱动的Redis客户端,其方法调用是异步的,Lettuce的API也是线程安全的,所以多个线程可以操作单个Lettuce连接来完成各种操作,同时Lettuce也支持连接池; Jedis切换Lettuce 依赖修改boot启动工程pom.xml改动 properties <lettuce.version>5.3.6.RELEASE</lettuce.version> <commons-pool2.version>2.8.1</commons-pool2.version> dependencies <dependency> <groupId>pro.shushi.pamirs.framework</groupId> <artifactId>pamirs-connectors-data-api</artifactId> <exclusions> <exclusion> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>${lettuce.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>${commons-pool2.version}</version> </dependency> 配置修改application.yml配置修改 spring: redis: database: 0 host: 127.0.0.1 port: 6379 prefix: pamirs timeout: 2000 # 可选 password: xxxxx # 可选 # cluster: # nodes: # – 127.0.0.1:6379 # timeout: 2000 # max-redirects: 7 lettuce: pool: enable: true max-idle: 16 min-idle: 1 max-active: 16 max-wait: 2000

    2024年2月2日
    1.1K00

Leave a Reply

Please Login to Comment