背景
后臺系統(tǒng)要支持第三方合作伙伴畏浆,且每個第三方開通的模塊都不相同鉴扫。以往的權(quán)限無法滿足需求身害。這里重新實現(xiàn)一套數(shù)據(jù)權(quán)限味悄。每個門診維護(hù)一套配置。
條件顯示處理器
Thymeleaf給我們提供了條件顯示處理器(AbstractConditionalVisibilityAttrProcessor
)塌鸯。我們只需要繼承這個處理器即可侍瑟。
@Component
public class ModuleEnableAttrProcessor extends AbstractConditionalVisibilityAttrProcessor {
//優(yōu)先級比sec:authorize(300)靠前。保證模塊先執(zhí)行丙猬。
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;
}
}
這里由于模塊權(quán)限優(yōu)先級比較高茧球,故設(shè)置precedence=AuthorizeAttrProcessor.ATTR_PRECEDENCE - 1庭瑰,只需要實現(xiàn)isVisible方法,返回true:顯示袜腥,false:不顯示钉汗。
復(fù)雜表達(dá)式的支持
由于門診首頁狀態(tài)欄比較特殊羹令,會有多個模塊共同決定是否顯示鲤屡,或者一些模塊啟用,一些模塊禁用等復(fù)雜情況福侈,這里借鑒security的表達(dá)式(不知道有沒有其它的更好的方法)酒来。
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
來解析復(fù)雜表達(dá)式肪凛,比如:BOOKING AND !EXAM
表示有BOOKING
配置堰汉,且沒有EXAM
配置。解析出來SpelNode
再逐步的判斷AND
,OR
,!
語法伟墙,最后得出結(jié)果翘鸭。代碼如下:
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
采用遞歸的方法調(diào)用_handleSpelNode
找到PropertyOrFieldReference
里面的值并處理。判斷是否設(shè)置了模塊權(quán)限拱烁,通過redis將配置緩存起來生蚁,減少數(shù)據(jù)庫的壓力。
初始化處理器
將條件顯示處理器增加到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<IProcessor> getProcessors() {
final Set<IProcessor> processors = new HashSet<IProcessor>();
processors.add(moduleEnableAttrProcessor);
return processors;
}
}
Thymeleaf模板應(yīng)用
只需要在標(biāo)簽中加入 DEFAULT_PREFIX:ATTR_NAME
屬性戏自,屬性的值即為條件表達(dá)式邦投。
<li data-type="warehouse" module:enable="MEDICINE or EXAM or CONSUMABLE" sec:authorize="hasPermission('warehouse', 'manage')"><a th:href="@{/warehouse/consume}"><i class="icon-inventory"></i>庫存</a></li>