提交 c69049d0 编写于 作者: J jarvis

feat: 1.增加guava依赖2. 增加mybatis-plus拦截器修改sql设置数据权限

上级 eb1ec526
......@@ -62,6 +62,7 @@
<docker.image.prefix>hub.zlt.com:8080/microservices-platform</docker.image.prefix>
<docker.java.security.egd>-Djava.security.egd=file:/dev/./urandom</docker.java.security.egd>
<docker.java.opts>-Xms128m -Xmx128m</docker.java.opts>
<guava.version>20.0</guava.version>
</properties>
<dependencies>
......@@ -430,6 +431,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
......
......@@ -7,6 +7,7 @@ import java.util.Map;
import java.util.Set;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.central.common.annotation.LoginUser;
import com.central.common.constant.CommonConstant;
import com.central.common.model.*;
......@@ -286,6 +287,23 @@ public class SysUserController {
return queryService.strQuery("sys_user", searchDto, SEARCH_LOGIC_DEL_DTO);
}
/**
* 获取用户并返回角色列表
* @param username
* @return
*/
@GetMapping(value = "/users/roleUser/{username}")
@ApiOperation(value = "查询用户-带角色信息")
@Cacheable(value = "userRoles", key = "#username")
public SysUser selectRoleUser(@PathVariable("username") String username){
SysUser sysUser = selectByUsername(username);
if(ObjectUtil.isNotNull(sysUser)){
List<SysRole> roleList = findRolesByUserId(sysUser.getId());
sysUser.setRoles(roleList);
}
return sysUser;
}
/**
* 是否超级管理员
*/
......
......@@ -89,5 +89,9 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.central.common.datascope.mp.interceptor;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.central.common.datascope.mp.sql.handler.SqlHandler;
import com.central.common.properties.DataScopeProperties;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.http.server.PathContainer;
import org.springframework.web.util.pattern.PathPatternParser;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import static com.central.common.datascope.mp.sql.handler.SqlHandler.ALIAS_SYNBOL;
/**
* 数据权限拦截器
*
* @author jarvis create by 2023/1/7
*/
@Slf4j
@Data
@NoArgsConstructor
public class DataScopeInnerInterceptor implements InnerInterceptor {
private DataScopeProperties dataScopeProperties;
/**
* 权限的where条件
*/
private SqlHandler sqlHandler;
/**
* 对表配置进行缓存,优先读取缓存,在进行匹配
*/
private Map<String, TableConfig> tableInfoMap = new HashMap<>();
/**
* 通配符
*/
private PathPatternParser pathPatternParser = new PathPatternParser();
public DataScopeInnerInterceptor(DataScopeProperties dataScopeProperties, SqlHandler sqlHandler) {
this.dataScopeProperties = dataScopeProperties;
this.sqlHandler = sqlHandler;
}
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
if(CollUtil.isEmpty(dataScopeProperties.getIgnoreSqls())|| !dataScopeProperties.getIgnoreSqls().contains(ms.getId())){
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
String sql = boundSql.getSql();
try {
Select select = explainQuerySql(sql);
reform(select.getSelectBody());
mpBs.sql(select.toString());
} catch (JSQLParserException e) {
throw new RuntimeException(e);
}
}
}
public Select explainQuerySql(String sql) throws JSQLParserException {
CCJSqlParserManager parserManager = new CCJSqlParserManager();
Select select = (Select) parserManager.parse(new StringReader(sql));
return select;
}
/**
* 递归对查询和解析后的子查询进行改造
* @param selectBody
* @param <T>
* @throws JSQLParserException
*/
public <T extends SelectBody>void reform(SelectBody selectBody) throws JSQLParserException {
// 如果是plainSelect的话进行改造
if(selectBody instanceof PlainSelect&& ObjectUtil.isNotNull(sqlHandler)){
PlainSelect select = (PlainSelect) selectBody;
// 获取权限的where条件
String scopeWhereSql = sqlHandler.handleScopeSql();
// 如果条件不是空的话才对select进行改造
if(StrUtil.isNotBlank(scopeWhereSql)){
// 需要改造的别名列表,自动增加到where条件中
List<String> tableAliasList = new ArrayList<>();
FromItem fromItem = select.getFromItem();
String tableAlias = explainFromItem(fromItem);
// 获取from的表字段,如果from是子查询则进行递归
if(fromItem instanceof Table){
String upperTableName = ((Table) fromItem).getName().toUpperCase();
if(tableInfoMap.containsKey(upperTableName)){
if (!tableInfoMap.get(upperTableName).getIgnore()) {
tableAliasList.add(StrUtil.isNotBlank(tableAlias)? tableAlias: "");
}
}else{
boolean ignore = true;
if(isReformTable(upperTableName)){
tableAliasList.add(StrUtil.isNotBlank(tableAlias)? tableAlias: "");
ignore = false;
}
// 写入缓存
tableInfoMap.put(upperTableName, new TableConfig(upperTableName, ignore));
}
}else if(fromItem instanceof SubSelect){
reform(((SubSelect) fromItem).getSelectBody());
}
// 获取join列表,然后获取对应的表或者递归子查询
List<Join> joinList = select.getJoins();
if (CollUtil.isNotEmpty(joinList)) {
for (Join join : joinList) {
if(join.getRightItem() instanceof Table){
String joinTable = ((Table) join.getRightItem()).getName().toUpperCase();
String joinAlias = ((Table) join.getRightItem()).getAlias().getName();
if(tableInfoMap.containsKey(joinTable)){
if (!tableInfoMap.get(joinTable).getIgnore()) {
tableAliasList.add(StrUtil.isNotBlank(joinAlias)? joinAlias: "");
}
}else{
boolean ignore = true;
if(isReformTable(joinTable)){
tableAliasList.add(StrUtil.isNotBlank(joinAlias)? joinAlias: "");
ignore = false;
}
// 写入缓存
tableInfoMap.put(joinTable, new TableConfig(joinTable, ignore));
}
}
if(join.getRightItem() instanceof SubSelect){
reform(((SubSelect) join.getRightItem()).getSelectBody());
}
}
}
// 如果改造的表是空的话则不改造对应的select
if(CollUtil.isNotEmpty(tableAliasList)){
reformWhere(select, scopeWhereSql, tableAliasList);
}
}
// 如果select不是plainSelect的话则进行递归改造
}else if(selectBody instanceof WithItem&& Objects.nonNull(((WithItem)selectBody).getSubSelect())){
reform(((WithItem)selectBody).getSubSelect().getSelectBody());
}
}
/**
* 判断表是否需要改造
* @param table
* @return
* 1. 判断表是否在需要改造的范围
* 1.1 如果表在inclde的set中(是否存在没用通配符的情况写入配置)
* 1.2 进行通配符匹配判断范围
* 2. 在改造的范围中进行提出
* 2.1 判断是不是完全匹配上ignore列表中
* 2.2 判断是否在通配符过滤
*/
private boolean isReformTable(String table){
return
// 1. 判断表是否在需要改造的范围
(dataScopeProperties.getIncludeTables().contains(table)
||dataScopeProperties.getIncludeTables().stream().anyMatch(item->
pathPatternParser.parse(item.toUpperCase()).matches(PathContainer.parsePath(table))
))&& (
// 如果没有忽略列表的话在范围中直接返回
CollUtil.isEmpty(dataScopeProperties.getIgnoreTables())
// 在改造的范围中进行忽略表
||!(dataScopeProperties.getIgnoreTables().contains(table)||
dataScopeProperties.getIgnoreTables().stream().anyMatch(item->
pathPatternParser.parse(item.toUpperCase()).matches(PathContainer.parsePath(table))
)));
}
/**
* 解析from中的东西
* @param fromItem
* @return
* @throws JSQLParserException
*/
private String explainFromItem(FromItem fromItem) throws JSQLParserException {
// 别名
String alias = "";
if(Objects.nonNull(fromItem)){
// 如果from的东西是表的话
if (fromItem instanceof Table) {
Alias tablealias = ((Table) fromItem).getAlias();
if(Objects.nonNull(tablealias)&& StrUtil.isNotBlank(tablealias.getName())){
alias = tablealias.getName();
}else{
alias = ALIAS_SYNBOL;
((Table) fromItem).setAlias(new Alias(ALIAS_SYNBOL, false));
}
}
// 如果from的子查询
if(fromItem instanceof SubSelect){
SelectBody subSelectBody = ((SubSelect) fromItem).getSelectBody();
reform(subSelectBody);
}
}
return alias;
}
/**
* 改造where条件
* @param select
* @param whereSql where 条件
* @param aliasName 需要添加权限的表别名
* @return
* @throws JSQLParserException
*/
private SelectBody reformWhere(PlainSelect select, String whereSql, List<String> aliasName) throws JSQLParserException {
Expression where = select.getWhere();
// todo 处理exists
if(StrUtil.isNotBlank(whereSql)&& CollUtil.isNotEmpty(aliasName)){
String andWhereSql = aliasName.stream()
.map(item -> whereSql.replaceAll(ALIAS_SYNBOL, StrUtil.isNotBlank(item) ? item : ""))
.collect(Collectors.joining(" and "));
if(StrUtil.isNotBlank(andWhereSql)){
Expression expression = CCJSqlParserUtil
.parseCondExpression(andWhereSql);
if(ObjectUtil.isNull(where)){
select.setWhere(expression);
}else {
AndExpression andExpression = new AndExpression(where, expression);
select.setWhere(andExpression);
}
}
}
return select;
}
public class TableConfig{
private String tableName;
private Boolean isIgnore;
public TableConfig(String tableName, Boolean isIgnore) {
this.tableName = tableName;
this.isIgnore = isIgnore;
}
public String getTableName() {
return tableName;
}
public Boolean getIgnore() {
return isIgnore;
}
}
}
package com.central.common.datascope.mp.sql.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.central.common.context.LoginUserContextHolder;
import com.central.common.enums.DataScope;
import com.central.common.feign.UserService;
import com.central.common.model.SysRole;
import com.central.common.model.SysUser;
import com.central.common.properties.DataScopeProperties;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* 个人权限的处理器
*
* @author jarvis create by 2023/1/10
*/
public class CreatorDataScopeSqlHandler implements SqlHandler{
@Autowired
UserService userService;
@Autowired
private DataScopeProperties dataScopeProperties;
/**
* 返回需要增加的where条件,返回空字符的话则代表不需要权限控制
*
* @return where条件
* 如果角色是全部权限的话则不进行控制,如果是个人权限的话则自动加入create_id = user_id
*/
@Override
public String handleScopeSql() {
SysUser user = LoginUserContextHolder.getUser();
Assert.notNull(user, "登陆人不能为空");
List<SysRole> roleList = userService.findRolesByUserId(user.getId());
return StrUtil.isBlank(dataScopeProperties.getScopeColumn())
||(CollUtil.isNotEmpty(roleList)&& roleList.stream().anyMatch(item-> DataScope.ALL.equals(item.getDataScope())))
? DO_NOTHING:
// 这里确保有配置权限范围控制的字段
// 1. 如果没有配置角色的情况默认采用只读个人创建的记录
// 2. 如果有配置角色的话判断是否存在有ALL的情况,如果没有ALL的话读取个人创建记录
String.format("%s.%s = '%s'", ALIAS_SYNBOL, dataScopeProperties.getScopeColumn(), user.getId());
}
}
package com.central.common.datascope.mp.sql.handler;
/**
* 示例
*
* @author jarvis create by 2023/1/8
*/
public class DefaultSqlHandler implements SqlHandler{
@Override
public String handleScopeSql() {
return DO_NOTHING;
}
}
package com.central.common.datascope.mp.sql.handler;
/**
* 数据权限的sql获取接口
*
* @author jarvis create by 2023/1/8
*/
public interface SqlHandler {
/**
* 通过这个字符替换成别名,自动的
*/
String ALIAS_SYNBOL = "alias_";
/**
* 空字符串
*/
String DO_NOTHING = "";
/**
* 返回需要增加的where条件,返回空字符的话则代表不需要权限控制
* @return where条件
*/
String handleScopeSql();
}
package com.central.common.enums;
import lombok.Getter;
/**
* 枚举类型
*
* @author jarvis create by 2023/1/10
*/
@Getter
public enum DataScope implements ZltEnum{
ALL(0, "全部权限"), CREATOR(1, "创建者权限");
DataScope(Integer id, String content) {
this.id = id;
this.content = content;
}
private Integer id;
private String content;
}
package com.central.common.enums;
/**
* 接口
*
* @author jarvis create by 2023/1/20
*/
public interface ZltEnum {
/**
* 获取id
* @return
*/
Integer getId();
/**
* 获取内容
* @return
*/
String getContent();
}
......@@ -3,12 +3,15 @@ package com.central.common.feign;
import com.central.common.constant.ServiceNameConstants;
import com.central.common.feign.fallback.UserServiceFallbackFactory;
import com.central.common.model.LoginAppUser;
import com.central.common.model.SysRole;
import com.central.common.model.SysUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @author zlt
*/
......@@ -48,4 +51,22 @@ public interface UserService {
*/
@GetMapping(value = "/users-anon/openId", params = "openId")
LoginAppUser findByOpenId(@RequestParam("openId") String openId);
/**
* 获取带角色的用户信息
* @param username
* @return
*/
@GetMapping(value = "/users/roleUser/{username}")
public SysUser selectRoleUser(@PathVariable("username") String username);
/**
* 获取用户的角色
*
* @param
* @return
*/
@GetMapping("/users/{id}/roles")
public List<SysRole> findRolesByUserId(@PathVariable("id") Long id);
}
......@@ -2,10 +2,14 @@ package com.central.common.feign.fallback;
import com.central.common.feign.UserService;
import com.central.common.model.LoginAppUser;
import com.central.common.model.SysRole;
import com.central.common.model.SysUser;
import org.springframework.cloud.openfeign.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
/**
* userService降级工场
*
......@@ -40,6 +44,29 @@ public class UserServiceFallbackFactory implements FallbackFactory<UserService>
log.error("通过openId查询用户异常:{}", openId, throwable);
return new LoginAppUser();
}
/**
* 获取带角色的用户信息
*
* @param username
* @return
*/
@Override
public SysUser selectRoleUser(String username) {
log.error("通过用户名查询用户异常:{}", username, throwable);
return new SysUser();
}
/**
* 获取用户的角色
*
* @param id@return
*/
@Override
public List<SysRole> findRolesByUserId(Long id) {
log.error("通过用户id查询角色列表异常:{}", id, throwable);
return Collections.emptyList();
}
};
}
}
......@@ -2,6 +2,7 @@ package com.central.common.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.central.common.enums.DataScope;
import lombok.Data;
import lombok.EqualsAndHashCode;
......@@ -18,4 +19,8 @@ public class SysRole extends SuperEntity {
private String name;
@TableField(exist = false)
private Long userId;
/**
* 数据权限字段
*/
private DataScope dataScope;
}
package com.central.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* 示例
*
* @author jarvis create by 2023/1/8
*/
@ConfigurationProperties(prefix = "zlt.datascope")
@Data
public class DataScopeProperties {
/**
* 是否开启权限控制
*/
private Boolean enabled = Boolean.FALSE;
/**
* 在includeTables的匹配符中过滤某几个表不需要权限的,仅enabled=true
*/
private Set<String> ignoreTables = Collections.emptySet();
/**
* 指定某几条sql不执行权限控制, 仅enabled=true生效
*/
private Set<String> ignoreSqls = Collections.emptySet();
/**
* 指定某几个表接受权限控制,仅enabled=true,默认当开启时全部表
*/
private Set<String> includeTables = Collections.singleton("*");
/**
* 指定需要的字段名
*/
private String scopeColumn;
}
......@@ -5,6 +5,10 @@ import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.central.common.datascope.mp.interceptor.DataScopeInnerInterceptor;
import com.central.common.datascope.mp.sql.handler.CreatorDataScopeSqlHandler;
import com.central.common.datascope.mp.sql.handler.SqlHandler;
import com.central.common.properties.DataScopeProperties;
import com.central.common.properties.TenantProperties;
import com.central.db.interceptor.CustomTenantInterceptor;
import com.central.db.properties.MybatisPlusAutoFillProperties;
......@@ -23,7 +27,7 @@ import org.springframework.context.annotation.Bean;
* Blog: https://zlt2000.gitee.io
* Github: https://github.com/zlt2000
*/
@EnableConfigurationProperties(MybatisPlusAutoFillProperties.class)
@EnableConfigurationProperties({MybatisPlusAutoFillProperties.class, DataScopeProperties.class})
public class MybatisPlusAutoConfigure {
@Autowired
private TenantLineHandler tenantLineHandler;
......@@ -34,11 +38,20 @@ public class MybatisPlusAutoConfigure {
@Autowired
private MybatisPlusAutoFillProperties autoFillProperties;
@Autowired
private DataScopeProperties dataScopeProperties;
@Bean
@ConditionalOnMissingBean
public SqlHandler sqlHandler(){
return new CreatorDataScopeSqlHandler();
}
/**
* 分页插件,自动识别数据库类型
*/
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
public MybatisPlusInterceptor paginationInterceptor(SqlHandler sqlHandler) {
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
boolean enableTenant = tenantProperties.getEnable();
//是否开启多租户隔离
......@@ -47,6 +60,9 @@ public class MybatisPlusAutoConfigure {
tenantLineHandler, tenantProperties.getIgnoreSqls());
mpInterceptor.addInnerInterceptor(tenantInterceptor);
}
if(dataScopeProperties.getEnabled()){
mpInterceptor.addInnerInterceptor(new DataScopeInnerInterceptor(dataScopeProperties, sqlHandler));
}
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mpInterceptor;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册