公司網(wǎng)站建設宣傳話語百度手機助手app下載并安裝
springBoot項目使用切面編程實現(xiàn)數(shù)據(jù)權限管理
- 什么是數(shù)據(jù)權限管理
- 如何實現(xiàn)數(shù)據(jù)權限管理
什么是數(shù)據(jù)權限管理
不同用戶在某頁面看到數(shù)據(jù)不一致,實現(xiàn)每個用戶之間數(shù)據(jù)隔離的效果。
如以下場景:
● 頁面期望展示當前登錄人所在部門的數(shù)據(jù)。
● 頁面期望展示當前登錄人所在部門及下級部門的數(shù)據(jù)。
● 頁面期望展示當前登錄人創(chuàng)建的數(shù)據(jù)。
如何實現(xiàn)數(shù)據(jù)權限管理
接下來我們實現(xiàn)一個簡單的數(shù)據(jù)權限控制,規(guī)則只包括自定義sql,目的是在需要的時候將自定義sql拼接到sql中,并將變量替換成對應的值。
1、首先確定用戶-角色-菜單-數(shù)據(jù)權限的關系
菜單有多個數(shù)據(jù)權限
角色可以綁定多個菜單的,綁定菜單時可以綁定數(shù)據(jù)權限
用戶與角色綁定。
2、定義注解,在方法上使用注解標識當前接口對應的菜單,為了查詢數(shù)據(jù)權限規(guī)則,同時該注解可以作為切面的切入點。
/*** 數(shù)據(jù)權限注解* @Author taoyan* @Date 2019年4月11日*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface PermissionData {/*** 配置菜單的組件路徑,用于數(shù)據(jù)權限*/String permissionId() default "";
}
3、定義數(shù)據(jù)權限規(guī)則信息對象
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "菜單權限規(guī)則表對象", description = "菜單權限規(guī)則表")
public class SysPermissionDataRule extends BaseEntity {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "id", required = true)@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;@ApiModelProperty(value = "對應的菜單id", required = true)@NotNull(message = "對應的菜單id不能為空")private Long permissionId;@ApiModelProperty(value = "規(guī)則名稱")private String ruleName;@ApiModelProperty(value = "規(guī)則值")private String ruleValue;@ApiModelProperty(value = "狀態(tài);1:有效,0:無效")private String status;
}
4、定義切面,將用戶信息和數(shù)據(jù)權限規(guī)則緩存再request中。
切面:
package com.sinosoft.springbootplus.datapermission.aspect;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData;
import com.sinosoft.springbootplus.core.context.RequestContext;
import com.sinosoft.springbootplus.datapermission.handler.PermissionDataDepInfoHandler;
import com.sinosoft.springbootplus.datapermission.handler.PermissionDataInfoHandler;
import com.sinosoft.springbootplus.system.domain.entity.SysPermissionDataRule;
import com.sinosoft.springbootplus.system.domain.service.SysPermissionDataRuleDomain;
import com.sinosoft.springbootplus.system.util.DataAutorUtils;
import com.sinosoft.springbootplus.util.HttpServletRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;/*** 數(shù)據(jù)權限切面處理類* 當被請求的方法有注解PermissionData時,會在往當前request中寫入數(shù)據(jù)權限信息* @Date 2019年4月10日* @Version: 1.0* @author: jeecg-boot*/
@Aspect
@Component
@Slf4j
public class PermissionDataAspect {@Pointcut("@annotation(com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData)")public void pointCut() {}@Around("pointCut()")public Object arround(ProceedingJoinPoint point) throws Throwable{HttpServletRequest request = HttpServletRequestUtil.getRequest();//將用戶信息放在request請求中MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();PermissionData pd = method.getAnnotation(PermissionData.class);String permissionId = pd.permissionId();//根據(jù)userId和菜單id獲取綁定的角色對應的數(shù)據(jù)權限List<SysPermissionDataRule> dataRules = sysPermissionDataRuleDomain.getPermissionDataRulesByCompentAndUserId(permissionId,loginUserId);if(dataRules != null && dataRules.size()>0){//將權限信息暫存在request中DataAutorUtils.installDataSearchConditon(request,dataRules);}//將用戶信息存儲在request中DataPermissionContext dataPermissionContext = new DataPermissionContext();//從handler中獲取用戶部門信息if(ObjectUtil.isNotEmpty(permissionDataDepInfoHandler)){Map<String, Object> userInfo = permissionDataDepInfoHandler.getUserInfo(request, loginUserId);dataPermissionContext.putAll(userInfo);}//從handler中獲取用戶其他信息if (CollUtil.isNotEmpty(permissionDataInfoHandlers)) {for (PermissionDataInfoHandler permissionDataInfoHandler:permissionDataInfoHandlers) {Map<String, Object> userInfo = permissionDataInfoHandler.getUserInfo(request, loginUserId);if (null != userInfo) {dataPermissionContext.putAll(userInfo);}}}DataAutorUtils.installUserInfo(dataPermissionContext);return point.proceed();}
}
切面中往request中放和取用戶信息、數(shù)據(jù)權限的工具類
public class DataAutorUtils {public static final String MENU_DATA_AUTHOR_RULES = "MENU_DATA_AUTHOR_RULES";public static final String SYS_USER_INFO = "SYS_USER_INFO";/*** 往鏈接請求里面,傳入數(shù)據(jù)查詢條件*/public static synchronized void installDataSearchConditon(HttpServletRequest request, List<SysPermissionDataRule> dataRules) {request.setAttribute(MENU_DATA_AUTHOR_RULES, dataRules);}/*** 獲取請求對應的數(shù)據(jù)權限規(guī)則*/@SuppressWarnings("unchecked")public static synchronized List<SysPermissionDataRule> loadDataSearchConditon() {return (List<SysPermissionDataRule>) HttpServletRequestUtil.getRequest().getAttribute(MENU_DATA_AUTHOR_RULES);}/*** 將用戶信息存到request*/public static synchronized void installUserInfo(DataPermissionContext dataPermissionContext) {HttpServletRequestUtil.getRequest().setAttribute(SYS_USER_INFO, dataPermissionContext);}/*** 從request獲取用戶信息*/public static synchronized DataPermissionContext loadUserInfo() {return (DataPermissionContext) HttpServletRequestUtil.getRequest().getAttribute(SYS_USER_INFO);}
}
5、在需要拼數(shù)據(jù)權限的接口上增加注解,并初始化queryWrapper,將request中的參數(shù)傳進去,包括了該菜單的數(shù)據(jù)權限和用戶信息
4、初始化queryWrapper將數(shù)據(jù)權限規(guī)則拼接到sql中,拼接時會將用戶信息map中的信息替換成自定義sql #{ }中的值,因此放用戶信息時map中的key與自定義sql中#{ }的值要對應。
/*** 查詢生成器*/
@Slf4j
public class QueryGenerator {public static final String SQL_RULES_COLUMN = "SQL_RULES_COLUMN";/*** 獲取查詢條件構造器QueryWrapper實例 通用查詢條件已被封裝完成* @param searchObj 查詢實體* @param parameterMap request.getParameterMap()* @return QueryWrapper實例*/public static <T> QueryWrapper<T> initQueryWrapper(T searchObj, Map<String, String[]> parameterMap){long start = System.currentTimeMillis();QueryWrapper<T> queryWrapper = new QueryWrapper<T>();installMplus(queryWrapper, searchObj, parameterMap);log.debug("---查詢條件構造器初始化完成,耗時:"+(System.currentTimeMillis()-start)+"毫秒----");return queryWrapper;}/*** 組裝Mybatis Plus 查詢條件* <p>使用此方法 需要有如下幾點注意:* <br>1.使用QueryWrapper 而非LambdaQueryWrapper;* <br>2.實例化QueryWrapper時不可將實體傳入?yún)?shù)* <br>錯誤示例:如QueryWrapper<JeecgDemo> queryWrapper = new QueryWrapper<JeecgDemo>(jeecgDemo);* <br>正確示例:QueryWrapper<JeecgDemo> queryWrapper = new QueryWrapper<JeecgDemo>();* <br>3.也可以不使用這個方法直接調用 {@link #initQueryWrapper}直接獲取實例*/private static void installMplus(QueryWrapper<?> queryWrapper,Object searchObj,Map<String, String[]> parameterMap) {Map<String,SysPermissionDataRule> ruleMap = getRuleMap();//權限規(guī)則自定義SQL表達式for (String c : ruleMap.keySet()) {if(c.startsWith(SQL_RULES_COLUMN)){queryWrapper.and(i ->i.apply(getSqlRuleValue(ruleMap.get(c).getRuleValue())));}}}/*** 獲取請求對應的數(shù)據(jù)權限規(guī)則*/public static Map<String, SysPermissionDataRule> getRuleMap() {Map<String, SysPermissionDataRule> ruleMap = new HashMap<>(5);List<SysPermissionDataRule> list = DataAutorUtils.loadDataSearchConditon();if(list != null&&list.size()>0){if(list.get(0)==null){return ruleMap;}for (SysPermissionDataRule rule : list) {String column = SQL_RULES_COLUMN+rule.getId();ruleMap.put(column, rule);}}return ruleMap;}public static String getSqlRuleValue(String sqlRule){try {Set<String> varParams = getSqlRuleParams(sqlRule);for(String var:varParams){String tempValue = converRuleValue(var);sqlRule = sqlRule.replace("#{"+var+"}",tempValue);}} catch (Exception e) {log.error(e.getMessage(), e);}return sqlRule;}/*** 獲取sql中的#{key} 這個key組成的set*/public static Set<String> getSqlRuleParams(String sql) {if(StringUtils.isEmpty(sql)){return null;}Set<String> varParams = new HashSet<String>();String regex = "\\#\\{\\w+\\}";Pattern p = Pattern.compile(regex);Matcher m = p.matcher(sql);while(m.find()){String var = m.group();varParams.add(var.substring(var.indexOf("{")+1,var.indexOf("}")));}return varParams;}public static String converRuleValue(String ruleValue) {String value = getUserSystemData(ruleValue);return value!= null ? value : ruleValue;}/*** 從當前用戶中獲取變量*/public static String getUserSystemData(String key) {DataPermissionContext dataPermissionContext = DataAutorUtils.loadUserInfo();Object o = dataPermissionContext.get(key);if(o instanceof String){return dataPermissionContext.get(key).toString();}if(o instanceof List){List<String> result = new ArrayList<>();for (Object item : (List<?>) o){result.add((String) item);}return "(" + String.join(",", result) + ")";}return null;}}