This commit is contained in:
清晨
2025-04-08 16:37:17 +08:00
commit 0c9d340a05
1659 changed files with 170293 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-base-support</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.7.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-data-rule</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl-framework-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,43 @@
package com.ruoyi.datarule.config;
import com.ruoyi.datarule.handler.DataRuleHandler;
import com.ruoyi.datarule.handler.DataRuleSqlHandler;
import com.ruoyi.datarule.interceptor.DataRuleInterceptor;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
@AllArgsConstructor
public class DataScopeConfiguration {
private JdbcTemplate jdbcTemplate;
@Bean
@ConditionalOnMissingBean(DataRuleHandler.class)
public DataRuleHandler dataRuleHandler() {
DataRuleHandler dataRuleHandler = new DataRuleHandler(jdbcTemplate);
dataRuleHandler.init();
return dataRuleHandler;
}
@Bean
@ConditionalOnBean(DataRuleHandler.class)
@ConditionalOnMissingBean(DataRuleSqlHandler.class)
public DataRuleSqlHandler dataScopeHandler(DataRuleHandler dataRuleHandler) {
return new DataRuleSqlHandler(dataRuleHandler);
}
@Bean
@ConditionalOnBean({DataRuleSqlHandler.class, DataRuleHandler.class})
@ConditionalOnMissingBean(DataRuleInterceptor.class)
public DataRuleInterceptor interceptor(DataRuleSqlHandler dataRuleSqlHandler, DataRuleHandler dataRuleHandler) {
return new DataRuleInterceptor(dataRuleSqlHandler, dataRuleHandler);
}
}

View File

@@ -0,0 +1,9 @@
package com.ruoyi.datarule.constant;
public interface DataScopeConstant {
String DEFAULT_FIELD = "deptId";
String DEFAULT_COLUMN = "dept_id";
String DEFAULT_TABLE = "sys_dept";
}

View File

@@ -0,0 +1,159 @@
package com.ruoyi.datarule.handler;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.datarule.model.DataRuleModel;
import com.ruoyi.datarule.model.RoleDataRuleModel;
import com.ruoyi.datarule.model.TreeEntityModel;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public class DataRuleHandler {
HashMap<String, List<String>> roleDataRuleModelHashMap = new HashMap<>();
HashMap<String, DataRuleModel> dataRuleModelHashMap = new HashMap<>();
HashMap<String, Map<String, Object>> userModelHashMap = new HashMap<>();
HashMap<String, List<TreeEntityModel>> treeEntityModelMap = new HashMap<>();
private final JdbcTemplate jdbcTemplate;
public void init() {
List<DataRuleModel> list = jdbcTemplate.query("select * from sys_data_rule where del_flag = ?", new Object[]{0}, new BeanPropertyRowMapper<>(DataRuleModel.class));
list.forEach(item -> {
dataRuleModelHashMap.put(item.getScopeClass(), item);
if (StringUtils.isEmpty(item.getTableName())) {
return;
}
List<TreeEntityModel> treeEntityModelList = jdbcTemplate.query(String.format("select * from %s where del_flag = ?", item.getTableName()), new Object[]{0}, new BeanPropertyRowMapper<>(TreeEntityModel.class));
treeEntityModelMap.put(item.getTableName(), treeEntityModelList);
});
refreshRole();
}
public DataRuleModel getDataRuleModel(String mapperId) {
if (dataRuleModelHashMap.containsKey(mapperId)) {
return dataRuleModelHashMap.get(mapperId);
}
return null;
}
public List<String> getChildren(String tableName, String id) {
if (!treeEntityModelMap.containsKey(tableName)) {
List<TreeEntityModel> treeEntityModelList = jdbcTemplate.query(String.format("select * from %s where del_flag = ?", tableName), new Object[]{0}, new BeanPropertyRowMapper<>(TreeEntityModel.class));
treeEntityModelMap.put(tableName, treeEntityModelList);
}
List<String> idList = new ArrayList<>();
List<TreeEntityModel> treeEntityModelList = treeEntityModelMap.get(tableName);
treeEntityModelList.forEach(item -> {
if (item.getId().equals(id) || (!StringUtils.isEmpty(item.getAncestors()) && item.getAncestors().contains(id + ","))) {
idList.add(item.getId());
}
});
return idList;
}
public void refreshTreeEntity(String tableName) {
List<TreeEntityModel> treeEntityModelList = jdbcTemplate.query(String.format("select * from %s where del_flag = ?", tableName), new Object[]{0}, new BeanPropertyRowMapper<>(TreeEntityModel.class));
treeEntityModelMap.put(tableName, treeEntityModelList);
}
public void refreshRole() {
List<RoleDataRuleModel> roleDataRuleModelList = jdbcTemplate.query("select * from sys_role_data_rule", new Object[]{}, new BeanPropertyRowMapper<>(RoleDataRuleModel.class));
roleDataRuleModelHashMap.clear();
roleDataRuleModelList.forEach(item -> {
if (!roleDataRuleModelHashMap.containsKey(item.getRoleId())) {
roleDataRuleModelHashMap.put(item.getRoleId(), new ArrayList<>());
}
roleDataRuleModelHashMap.get(item.getRoleId()).add(item.getDataRuleId());
});
}
public void refreshDataRule(String id) {
DataRuleModel dataRuleModel = jdbcTemplate.queryForObject("select * from sys_data_rule where id = ?", new Object[]{id}, new BeanPropertyRowMapper<>(DataRuleModel.class));
for (Map.Entry<String, DataRuleModel> entry : dataRuleModelHashMap.entrySet()) {
if (entry.getValue().getId().equals(id)) {
dataRuleModelHashMap.remove(entry.getKey());
break;
}
}
dataRuleModelHashMap.put(dataRuleModel.getScopeClass(), dataRuleModel);
if (StringUtils.isEmpty(dataRuleModel.getTableName())) {
return;
}
List<TreeEntityModel> treeEntityModelList = jdbcTemplate.query(String.format("select * from %s where del_flag = ?", dataRuleModel.getTableName()), new Object[]{0}, new BeanPropertyRowMapper<>(TreeEntityModel.class));
treeEntityModelMap.put(dataRuleModel.getTableName(), treeEntityModelList);
}
public void deleteDataRule(String id) {
for (Map.Entry<String, DataRuleModel> entry : dataRuleModelHashMap.entrySet()) {
if (entry.getValue().getId().equals(id)) {
dataRuleModelHashMap.remove(entry.getKey());
break;
}
}
}
public DataRuleModel getDataRule(String mapperId, List<SysRole> roleList) {
for (SysRole sysRole : roleList) {
String roleId = sysRole.getRoleId().toString();
DataRuleModel dataRuleModel = getDataRuleModel(mapperId);
if (dataRuleModel == null) {
return null;
}
if (!roleDataRuleModelHashMap.containsKey(roleId)) {
//如果找不到,刷新角色权限
refreshRole();
//如果还不找不到返回null
if (!roleDataRuleModelHashMap.containsKey(roleId)) {
continue;
}
}
boolean isMatch = roleDataRuleModelHashMap.get(roleId).stream().anyMatch(dataRuleModel.getId()::contains);
if (isMatch) {
return dataRuleModel;
}
}
return null;
}
public void refreshUser(String id) {
SysUser userModel = jdbcTemplate.queryForObject("select * from sys_user where user_id = ?", new Object[]{id}, new BeanPropertyRowMapper<>(SysUser.class));
try {
Map<String, Object> map = BeanUtils.convertBean(userModel);
userModelHashMap.put(id, map);
} catch (IntrospectionException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public Map<String, Object> getUser(Long id) {
if (!userModelHashMap.containsKey(id)) {
refreshUser("" + id);
}
if (userModelHashMap.containsKey(id)) {
return userModelHashMap.get(id);
}
return null;
}
}

View File

@@ -0,0 +1,129 @@
package com.ruoyi.datarule.handler;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.util.JdbcConstants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.datarule.model.DataRuleModel;
import lombok.RequiredArgsConstructor;
import org.apache.commons.text.StringEscapeUtils;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.beetl.core.statement.ErrorGrammarProgram;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@RequiredArgsConstructor
public class DataRuleSqlHandler {
protected Logger logger = LoggerFactory.getLogger(getClass());
private static GroupTemplate groupTemplate;
@Value("${spring.datasource.driverClassName}")
String driverClassName;
static {
StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
Configuration configuration = null;
try {
configuration = Configuration.defaultConfiguration();
} catch (IOException e) {
e.printStackTrace();
}
groupTemplate = new GroupTemplate(resourceLoader, configuration);
}
private final DataRuleHandler dataRuleHandler;
private DbType getDataSourceType() {
if (driverClassName.contains("mysql")) {
return JdbcConstants.MYSQL;
} else if (driverClassName.contains("sqlserver")) {
return JdbcConstants.SQL_SERVER;
} else if (driverClassName.contains("oracle")) {
return JdbcConstants.ORACLE;
} else {
return JdbcConstants.MYSQL;
}
}
public String sqlCondition(DataRuleModel dataScope, LoginUser principal, String originalSql) {
//判断数据权限类型并组装对应Sql
Integer scopeRule = Objects.requireNonNull(dataScope).getScopeType();
if (DataRuleModel.ALL == scopeRule) {
return null;
}
Map<String, Object> userMap = dataRuleHandler.getUser(principal.getUserId());
SQLSelectStatement selectStmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(originalSql, getDataSourceType());
SQLSelectQueryBlock mySqlSelectQueryBlock = (SQLSelectQueryBlock) selectStmt.getSelect().getQuery();
SQLLimit limit = mySqlSelectQueryBlock.getLimit();
mySqlSelectQueryBlock.setLimit(null);
String tempSql = selectStmt.toString();
String id;
String result = "";
if (DataRuleModel.CUSTOM == scopeRule) {
Template template = groupTemplate.getTemplate(StringEscapeUtils.unescapeHtml4(dataScope.getScopeValue()));
if (template.program instanceof ErrorGrammarProgram) {
logger.info(((ErrorGrammarProgram) template.program).getException().detailCode);
}
userMap.forEach(template::binding);
String whereSql = template.render();
result = String.format(" select %s from (%s) scope " + whereSql, dataScope.getScopeField(), tempSql);
} else if (DataRuleModel.OWN == scopeRule) {
String whereSql = "where scope.%s = '%s'";
id = principal.getUserId().toString();
result = String.format(" select %s from (%s) scope " + whereSql, dataScope.getScopeField(), tempSql, dataScope.getScopeColumn(), id);
} else if (DataRuleModel.OWN_ORG == scopeRule) {
id = userMap.get(dataScope.getUserEntityField()).toString();
if (StringUtils.isEmpty(id)) {
logger.error(String.format("DataRuleSqlHandler OWN_ORG error id is not exist userId:%s,UserColumn%s", principal.getUserId(), dataScope.getUserEntityField()));
return null;
}
result = String.format("select %s from (%s) scope, %s a, sys_user sysu where a.id = sysu.%s" +
" and sysu.id = scope.%s and a.id = '%s'",
dataScope.getScopeField(), tempSql,
dataScope.getTableName(),
dataScope.getUserColumn(), dataScope.getScopeColumn(),
id);
} else if (DataRuleModel.OWN_ORG_CHILDREN == scopeRule) {
id = userMap.get(dataScope.getUserEntityField()).toString();
if (StringUtils.isEmpty(id)) {
logger.error(String.format("DataRuleSqlHandler OWN_ORG_CHILDREN error id is not exist userId:%s,UserColumn%s", principal.getUserId(), dataScope.getUserEntityField()));
return null;
}
List<Serializable> ids = new ArrayList<>();
ids.add(id);
List<String> deptIdList = dataRuleHandler.getChildren(dataScope.getTableName(), id);
ids.addAll(deptIdList);
id = StringUtils.join(ids, "','");
result = String.format("select %s from (%s) scope, %s a, sys_user sysu where a.id = sysu.%s" +
" and sysu.id = scope.%s and a.id in ('%s')",
dataScope.getScopeField(), tempSql,
dataScope.getTableName(),
dataScope.getUserColumn(), dataScope.getScopeColumn(),
id);
}
SQLSelectStatement selectStmtResult = (SQLSelectStatement) SQLUtils.parseSingleStatement(result, getDataSourceType());
SQLSelectQueryBlock mySqlSelectQueryBlockResult = (SQLSelectQueryBlock) selectStmtResult.getSelect().getQuery();
mySqlSelectQueryBlockResult.setLimit(limit);
logger.info(String.format("result sql: %s", selectStmtResult));
return selectStmtResult.toString();
}
}

View File

@@ -0,0 +1,108 @@
package com.ruoyi.datarule.interceptor;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.datarule.handler.DataRuleHandler;
import com.ruoyi.datarule.handler.DataRuleSqlHandler;
import com.ruoyi.datarule.model.DataRuleModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Properties;
@Slf4j
@RequiredArgsConstructor
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataRuleInterceptor extends AbstractSqlParserHandler implements Interceptor {
private final DataRuleSqlHandler dataRuleSqlHandler;
private final DataRuleHandler dataRuleHandler;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//未取到用户则放行
LoginUser principal = SecurityUtils.getLoginUser();
if (principal == null) {
return invocation.proceed();
}
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
this.sqlParser(metaObject);
//非SELECT操作放行
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()
|| StatementType.CALLABLE == mappedStatement.getStatementType()) {
return invocation.proceed();
}
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
//注解为空并且数据权限方法名未匹配到,则放行
String mapperId = mappedStatement.getId();
mapperId = mapperId.replace("_mpCount","");
DataRuleModel dataScope = dataRuleHandler.getDataRule(mapperId, principal.getUser().getRoles());;
//如果还不行,那就只有不处理了
if (dataScope == null) {
return invocation.proceed();
}
// 如果包含_mpCount说明是分页也需要过滤
if (mappedStatement.getId().contains("_mpCount")){
dataScope = BeanUtils.clone(dataScope);
dataScope.setScopeField("COUNT(*)");
originalSql = originalSql.replace("COUNT(*)", "*");
}
//获取数据权限规则对应的筛选Sql
String sqlCondition = dataRuleSqlHandler.sqlCondition(dataScope, principal, originalSql);
if (StringUtils.isBlank(sqlCondition)) {
return invocation.proceed();
} else {
metaObject.setValue("delegate.boundSql.sql", sqlCondition);
return invocation.proceed();
}
}
/**
* 生成拦截对象的代理
*
* @param target 目标对象
* @return 代理对象
*/
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
/**
* mybatis配置的属性
*
* @param properties mybatis配置的属性
*/
@Override
public void setProperties(Properties properties) {
}
}

View File

@@ -0,0 +1,46 @@
package com.ruoyi.datarule.model;
import com.ruoyi.datarule.constant.DataScopeConstant;
import lombok.Data;
import java.io.Serializable;
@Data
public class DataRuleModel implements Serializable {
private static final long serialVersionUID = 1L;
public static final int ALL = 1; // 全部
public static final int OWN = 2; // 本人可见
public static final int OWN_ORG = 3; // 所在机构可见
public static final int OWN_ORG_CHILDREN = 4; // 所在机构及子级可见
public static final int CUSTOM = 5; // 自定义
private String id;
/**
* 数据权限字段
*/
private String scopeColumn = DataScopeConstant.DEFAULT_COLUMN;
private String userColumn;
private String userEntityField = DataScopeConstant.DEFAULT_FIELD;
private String tableName = DataScopeConstant.DEFAULT_TABLE; //数据权限关联表名
/**
* 数据权限规则
*/
private Integer scopeType = ALL;
/**
* 可见字段
*/
private String scopeField;
/**
* 数据权限规则值
*/
private String scopeValue;
private String scopeClass;
}

View File

@@ -0,0 +1,28 @@
package com.ruoyi.datarule.model;
import lombok.Data;
import java.io.Serializable;
/**
* All rights Reserved, Designed By www.sunseagear.com
*
* @version V1.0
* @package sys
* @title: 角色数据权限关联表控制器
* @description: 角色数据权限关联表控制器
* @author: 未知
* @date: 2019-11-29 03:21:37
* @copyright: www.sunseagear.com Inc. All rights reserved.
*/
@Data
@SuppressWarnings("serial")
public class RoleDataRuleModel implements Serializable {
private String id; //id
private Integer dataRuleCategory; //权限类型(1:数据权限、2:接口权限)
private String dataRuleId; //权限id
private String roleId; //角色id
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.datarule.model;
import com.ruoyi.common.core.domain.TreeEntity;
import lombok.Data;
/**
* 树抽象实体基类
*/
@Data
public class TreeEntityModel extends TreeEntity {
protected String id; // 编号
protected String name; // 资源名称
}