|
@@ -0,0 +1,219 @@
|
|
|
|
+package com.ims.eval.config.permission;
|
|
|
|
+
|
|
|
|
+import cn.hutool.core.util.ClassUtil;
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
|
|
|
+import com.ims.eval.cache.CacheContext;
|
|
|
|
+import com.ims.eval.entity.DataDictionary;
|
|
|
|
+import com.ims.eval.entity.dto.response.MyuserResDTO;
|
|
|
|
+import com.ims.eval.feign.RemoteServiceBuilder;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import net.sf.jsqlparser.JSQLParserException;
|
|
|
|
+import net.sf.jsqlparser.expression.Expression;
|
|
|
|
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
|
|
|
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
|
|
|
+import net.sf.jsqlparser.statement.select.PlainSelect;
|
|
|
|
+import net.sf.jsqlparser.statement.select.Select;
|
|
|
|
+import org.apache.ibatis.executor.statement.StatementHandler;
|
|
|
|
+import org.apache.ibatis.mapping.MappedStatement;
|
|
|
|
+import org.apache.ibatis.plugin.Interceptor;
|
|
|
|
+import org.apache.ibatis.plugin.Intercepts;
|
|
|
|
+import org.apache.ibatis.plugin.Invocation;
|
|
|
|
+import org.apache.ibatis.plugin.Signature;
|
|
|
|
+import org.apache.ibatis.reflection.MetaObject;
|
|
|
|
+import org.apache.ibatis.reflection.SystemMetaObject;
|
|
|
|
+import org.aspectj.lang.JoinPoint;
|
|
|
|
+import org.aspectj.lang.annotation.Aspect;
|
|
|
|
+import org.aspectj.lang.annotation.Before;
|
|
|
|
+import org.aspectj.lang.annotation.Pointcut;
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
+import org.springframework.web.context.request.RequestContextHolder;
|
|
|
|
+import org.springframework.web.context.request.ServletRequestAttributes;
|
|
|
|
+import springfox.documentation.annotations.Cacheable;
|
|
|
|
+
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
+import java.sql.Connection;
|
|
|
|
+import java.util.*;
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @author gyee
|
|
|
|
+ */
|
|
|
|
+@Slf4j
|
|
|
|
+@Aspect
|
|
|
|
+@Component
|
|
|
|
+@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
|
|
|
|
+public class PermissionAspect implements Interceptor {
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private RemoteServiceBuilder serviceBuilder;
|
|
|
|
+
|
|
|
|
+ //扫描的包路径,需要权限的加在mapper类及方法上
|
|
|
|
+ private String packagePath = "com.ims.eval.dao";
|
|
|
|
+ private final static String DEPT_ID = "dept";
|
|
|
|
+ private final static String DEPT_MARK = "BM0001";
|
|
|
|
+
|
|
|
|
+ /** start 以下定义的数据需要和部门表一致 **/
|
|
|
|
+ //全部数据权限
|
|
|
|
+ private static final Integer DATA_SCOPE_ALL = 1;
|
|
|
|
+ //部门数据权限
|
|
|
|
+ private static final Integer DATA_SCOPE_DEPT = 2;
|
|
|
|
+ //个人数据权限
|
|
|
|
+ private static final Integer DATA_SCOPE_CUSTOM = 3;
|
|
|
|
+ /** end 以下定义的数据需要和部门表一致 **/
|
|
|
|
+
|
|
|
|
+ //请求头code
|
|
|
|
+ private String code;
|
|
|
|
+ //缓存权限类名
|
|
|
|
+ private static List<String> classNames;
|
|
|
|
+ //缓存权限类名+方法名
|
|
|
|
+ private static Map<String, Boolean> methodNames = new HashMap<>();
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 对TestService类下面的所有方法拦截.
|
|
|
|
+ */
|
|
|
|
+ @Pointcut("execution(* com.ims.eval.controller.*.*(..))")
|
|
|
|
+ public void pointcut() {
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //前置通知
|
|
|
|
+ @Before("pointcut()")
|
|
|
|
+ public void beforeMethod(JoinPoint joinPoint) {
|
|
|
|
+ ServletRequestAttributes attributes =
|
|
|
|
+ (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
|
|
+ HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
|
|
|
|
+ code = request.getHeader("code");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Object intercept(Invocation invocation) throws Throwable {
|
|
|
|
+ MyuserResDTO user = getSysUser(code);
|
|
|
|
+ DataDictionary dept = getSysDept(user);
|
|
|
|
+ if (user == null || dept == null) {
|
|
|
|
+ return invocation.proceed();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Integer scope = dept.getScope();
|
|
|
|
+ if (scope.equals(DATA_SCOPE_ALL)){
|
|
|
|
+ return invocation.proceed();
|
|
|
|
+ }
|
|
|
|
+ if (scope.equals(DATA_SCOPE_DEPT)) {
|
|
|
|
+ String deptId = dept.getDataKey();
|
|
|
|
+ try {
|
|
|
|
+ //反射扫包会比较慢,这里做了个懒加载
|
|
|
|
+ if (classNames == null) {
|
|
|
|
+ //扫描指定包路径下所有包含指定注解的类
|
|
|
|
+ Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(packagePath, DataPermission.class);
|
|
|
|
+ if (classSet == null && classSet.size() == 0) {
|
|
|
|
+ classNames = new ArrayList<>();
|
|
|
|
+ } else {
|
|
|
|
+ //取得类全名
|
|
|
|
+ classNames = classSet.stream().map(Class::getName).collect(Collectors.toList());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 拿到mybatis的一些对象
|
|
|
|
+ StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
|
|
|
|
+ MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
|
|
|
|
+ MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
|
|
|
+
|
|
|
|
+ // mappedStatement.getId()为执行的mapper方法的全路径名,newId为执行的mapper方法的类全名
|
|
|
|
+ String newId = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."));
|
|
|
|
+ // 如果不是指定的方法,直接结束拦截
|
|
|
|
+ if (!classNames.contains(newId)) {
|
|
|
|
+ return invocation.proceed();
|
|
|
|
+ }
|
|
|
|
+ String newName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());
|
|
|
|
+ Class<?> clazz = Class.forName(newId);
|
|
|
|
+
|
|
|
|
+ if (!methodNames.containsKey(newId + "-" + newName)){
|
|
|
|
+ for (Method method : clazz.getDeclaredMethods()) {
|
|
|
|
+ //方法是否含有DataPermission注解,如果含有注解则将数据结果过滤
|
|
|
|
+ if (method.isAnnotationPresent(DataPermission.class)) {
|
|
|
|
+ DataPermission dataPermission = method.getAnnotation(DataPermission.class);
|
|
|
|
+ if (dataPermission != null) {
|
|
|
|
+ methodNames.put(newId + "-" + method.getName(), dataPermission.isPermission());
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ methodNames.put(newId + "-" + method.getName(), true);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //是否开启数据权限
|
|
|
|
+ boolean isPermission = true;
|
|
|
|
+ isPermission = methodNames.get(newId + "-" + newName);
|
|
|
|
+ if (isPermission) {
|
|
|
|
+ // 获取到原始sql语句
|
|
|
|
+ String sql = statementHandler.getBoundSql().getSql();
|
|
|
|
+ // 解析并返回新的SQL语句,只处理查询sql
|
|
|
|
+ if (mappedStatement.getSqlCommandType().toString().equals("SELECT")) {
|
|
|
|
+ sql = getSql(sql, deptId);
|
|
|
|
+ }
|
|
|
|
+ // 修改sql
|
|
|
|
+ metaObject.setValue("delegate.boundSql.sql", sql);
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ log.error("数据权限隔离异常:", e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return invocation.proceed();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 解析SQL语句,并返回新的SQL语句
|
|
|
|
+ * 注意,该方法使用了JSqlParser来操作SQL,该依赖包Mybatis-plus已经集成了。如果要单独使用,请先自行导入依赖
|
|
|
|
+ *
|
|
|
|
+ * @param sql 原SQL
|
|
|
|
+ * @return 新SQL
|
|
|
|
+ */
|
|
|
|
+ private String getSql(String sql, String deptId) {
|
|
|
|
+ try {
|
|
|
|
+ // 修改原语句
|
|
|
|
+ String condition = DEPT_ID + "='" + deptId + "'";
|
|
|
|
+ Select select = (Select) CCJSqlParserUtil.parse(sql);
|
|
|
|
+ PlainSelect plainSelect = (PlainSelect)select.getSelectBody();
|
|
|
|
+ //取得原SQL的where条件
|
|
|
|
+ final Expression expression = plainSelect.getWhere();
|
|
|
|
+ //增加新的where条件
|
|
|
|
+ final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition);
|
|
|
|
+ if (expression == null) {
|
|
|
|
+ plainSelect.setWhere(envCondition);
|
|
|
|
+ } else {
|
|
|
|
+ AndExpression andExpression = new AndExpression(expression, envCondition);
|
|
|
|
+ plainSelect.setWhere(andExpression);
|
|
|
|
+ }
|
|
|
|
+ return plainSelect.toString();
|
|
|
|
+ } catch (JSQLParserException e) {
|
|
|
|
+ log.error("解析原SQL并构建新SQL错误:" + e);
|
|
|
|
+ return sql;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Object plugin(Object target) {
|
|
|
|
+ return Interceptor.super.plugin(target);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void setProperties(Properties properties) {
|
|
|
|
+ Interceptor.super.setProperties(properties);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Cacheable(value = "user_code")
|
|
|
|
+ private MyuserResDTO getSysUser(String code) {
|
|
|
|
+ Object json = serviceBuilder.getGatewayUrl().getSysUser(code);
|
|
|
|
+ MyuserResDTO user = JSON.parseObject(json.toString(), MyuserResDTO.class);
|
|
|
|
+ return user;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private DataDictionary getSysDept(MyuserResDTO user){
|
|
|
|
+ if (user != null && CacheContext.ddSuperKeyMap.containsKey(DEPT_MARK)){
|
|
|
|
+ Optional<DataDictionary> any = CacheContext.ddSuperKeyMap.get(DEPT_MARK).stream().filter(t -> t.getKeyName().equals(user.getDeptName())).findAny();
|
|
|
|
+ return any.isPresent() ? any.get() : null;
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+}
|