Thymeleaf自定义标签实现自定义数据权限

背景

后台系统要支持第三方合作伙伴,且每个第三方开通的模块都不相同。以往的权限无法满足需求。这里重新实现一套数据权限。每个门诊维护一套配置。

条件显示处理器

Thymeleaf给我们提供了条件显示处理器(AbstractConditionalVisibilityAttrProcessor)。我们只需要继承这个处理器即可。

@Component
public class ModuleEnableAttrProcessor extends AbstractConditionalVisibilityAttrProcessor {

    //优先级比sec:authorize(300)靠前。保证模块先执行。
    public static final int ATTR_PRECEDENCE = AuthorizeAttrProcessor.ATTR_PRECEDENCE - 1;
    public static final String ATTR_NAME = "enable";

    @Autowired
    @Lazy
    private ModuleEnableExpressionService moduleEnableExpressionService;

    public ModuleEnableAttrProcessor() {
        super(ATTR_NAME);
    }

    //控制显示与否的方法。
    @Override
    protected boolean isVisible(Arguments arguments, Element element, String attributeName) {
        //自定义属性值。
        final String attributeValue = element.getAttributeValue(attributeName);
        Integer clinicId = ClinicIdContextHolder.getClinicId();
        return moduleEnableExpressionService.parseExpressionByClinicId(clinicId, attributeValue);
    }

    @Override
    public int getPrecedence() {
        return ATTR_PRECEDENCE;
    }
}

这里由于模块权限优先级比较高,故设置precedence=AuthorizeAttrProcessor.ATTR_PRECEDENCE - 1,只需要实现isVisible方法,返回true:显示,false:不显示。

复杂表达式的支持

由于门诊首页状态栏比较特殊,会有多个模块共同决定是否显示,或者一些模块启用,一些模块禁用等复杂情况,这里借鉴security的表达式(不知道有没有其它的更好的方法)。

    public Boolean parseExpressionByClinicId(Integer clinicId, String expression){
        if(StringUtils.isBlank(expression)) return false;
        SpelExpressionParser template = new SpelExpressionParser();
        SpelExpression spelExpression = (SpelExpression)template.parseExpression(expression);
        return _handleSpelNode(clinicId, spelExpression.getAST());
    }

这里取巧,通过SpelExpressionParser来解析复杂表达式,比如:BOOKING AND !EXAM表示有BOOKING配置,且没有EXAM配置。解析出来SpelNode再逐步的判断AND,OR,!语法,最后得出结果。代码如下:

private Boolean _handleSpelNode(Integer clinicId, SpelNode ast){
        Class clazz = ast.getClass();
        if(clazz == OpAnd.class){
            return _handleAnd(clinicId, (OpAnd)ast);//处理and
        } else if(clazz == OpOr.class){
            return _handleOr(clinicId, (OpOr)ast);//处理or
        } else if(clazz == OperatorNot.class){
            return _handleNot(clinicId, (OperatorNot)ast);//处理!
        } else if(clazz == PropertyOrFieldReference.class){
            return _handleReference(clinicId, (PropertyOrFieldReference)ast);
        } else {
            return false;
        }
    }
    
    private boolean _handleReference(Integer clinicId, PropertyOrFieldReference reference){
        String name = reference.getName().toUpperCase();
        return clinicModuleService.isModuleEnabled(clinicId, ClinicModule.byName(name));
    }

这里的_handleAnd_handleOr_handleNot采用递归的方法调用_handleSpelNode找到PropertyOrFieldReference里面的值并处理。判断是否设置了模块权限,通过redis将配置缓存起来,减少数据库的压力。

初始化处理器

将条件显示处理器增加到thymeleaf处理器中


@Component
public class ModuleEnableDialect extends AbstractDialect {

    public static final String DEFAULT_PREFIX = "module";

    @Autowired
    private ModuleEnableAttrProcessor moduleEnableAttrProcessor;

    @Override
    public String getPrefix() {
        return DEFAULT_PREFIX;
    }

    @Override
    public Set getProcessors() {
        final Set processors = new HashSet();
        processors.add(moduleEnableAttrProcessor);
        return processors;
    }
}

Thymeleaf模板应用

只需要在标签中加入 DEFAULT_PREFIX:ATTR_NAME属性,属性的值即为条件表达式。

    
  • 库存
  • 你可能感兴趣的:(Thymeleaf自定义标签实现自定义数据权限)