🎉 3.2.0.RELEASE 新增灵活数据权限特性

This commit is contained in:
smallchill 2021-11-05 01:08:59 +08:00
parent 08187b2e1f
commit 3468b0f694
48 changed files with 1541 additions and 58 deletions

View File

@ -1,7 +1,7 @@
<p align="center">
<img src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg" alt="Build Status">
<img src="https://img.shields.io/badge/Spring%20Cloud-2020-blue.svg" alt="Coverage Status">
<img src="https://img.shields.io/badge/Spring%20Boot-2.5.2-blue.svg" alt="Downloads">
<img src="https://img.shields.io/badge/Spring%20Boot-2.5.6-blue.svg" alt="Downloads">
</p>
## SpringBlade微服务开发平台
@ -45,7 +45,8 @@ blade-tool
* 交流二群:`751253339`(满)
* 交流三群:`784729540`(满)
* 交流四群:`1034621754`(满)
* 交流五群:`946350912`
* 交流五群:`946350912`(满)
* 交流六群: `511624269`
## 在线演示
* Sword演示地址[https://sword.bladex.vip](https://sword.bladex.vip)
@ -166,10 +167,4 @@ LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求
## 鸣谢
* mica[Mica](https://github.com/lets-mica/mica)
* 如梦技术([DreamLu](https://www.dreamlu.net/)
* pigx[Pig Microservice](https://www.pig4cloud.com/zh-cn/)
* avue[avue](https://avue.top/)
* gitee.ltd[gitee.ltd](https://gitee.ltd/)
* 鲸宵(<a href="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/jx.png" target="_blank">鲸宵</a>
## 关注我们
![](https://images.gitee.com/uploads/images/2019/0330/065148_f0ada806_410595.jpeg)
* avue[avue](https://avuejs.com/)

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.boot.tenant;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 租户拦截器
*
* @author Chill
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BladeTenantInterceptor extends TenantLineInnerInterceptor {
/**
* 租户处理器
*/
private TenantLineHandler tenantLineHandler;
@Override
public void setTenantLineHandler(TenantLineHandler tenantLineHandler) {
super.setTenantLineHandler(tenantLineHandler);
this.tenantLineHandler = tenantLineHandler;
}
}

View File

@ -16,13 +16,15 @@
package org.springblade.core.boot.tenant;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.config.MybatisPlusConfiguration;
import org.springblade.core.mp.config.MybatisPlusConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 多租户配置类
@ -36,19 +38,29 @@ import org.springframework.context.annotation.Configuration;
public class TenantConfiguration {
/**
* 多租户配置类
*/
private final BladeTenantProperties properties;
/**
* 自定义租户处理器
* 自定义多租户处理器
*
* @param tenantProperties 多租户配置类
* @return TenantHandler
*/
@Bean
@ConditionalOnMissingBean(TenantLineHandler.class)
public TenantLineHandler bladeTenantHandler() {
return new BladeTenantHandler(properties);
@Primary
public TenantLineHandler bladeTenantHandler(BladeTenantProperties tenantProperties) {
return new BladeTenantHandler(tenantProperties);
}
/**
* 自定义租户拦截器
*
* @param tenantHandler 多租户处理器
* @return BladeTenantInterceptor
*/
@Bean
@Primary
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantHandler) {
BladeTenantInterceptor tenantInterceptor = new BladeTenantInterceptor();
tenantInterceptor.setTenantLineHandler(tenantHandler);
return tenantInterceptor;
}
/**

View File

@ -101,7 +101,7 @@ mybatis-plus:
swagger:
title: SpringBlade 接口文档系统
description: SpringBlade 接口文档系统
version: 3.1.0
version: 3.2.0
license: Powered By SpringBlade
licenseUrl: https://bladex.vip
terms-of-service-url: https://bladex.vip

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -0,0 +1,37 @@
<?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>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-core-datascope</artifactId>
<name>${project.artifactId}</name>
<version>${blade.tool.version}</version>
<packaging>jar</packaging>
<dependencies>
<!--Mybatis-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-mybatis</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<!--Jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.annotation;
import org.springblade.core.datascope.constant.DataScopeConstant;
import org.springblade.core.datascope.enums.DataScopeEnum;
import java.lang.annotation.*;
/**
* 数据权限定义
*
* @author Chill
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataAuth {
/**
* 资源编号
*/
String code() default "";
/**
* 数据权限对应字段
*/
String column() default DataScopeConstant.DEFAULT_COLUMN;
/**
* 数据权限规则
*/
DataScopeEnum type() default DataScopeEnum.ALL;
/**
* 可见字段
*/
String field() default "*";
/**
* 数据权限规则值域
*/
String value() default "";
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.config;
import lombok.AllArgsConstructor;
import org.springblade.core.datascope.handler.BladeDataScopeHandler;
import org.springblade.core.datascope.handler.BladeScopeModelHandler;
import org.springblade.core.datascope.handler.DataScopeHandler;
import org.springblade.core.datascope.handler.ScopeModelHandler;
import org.springblade.core.datascope.interceptor.DataScopeInterceptor;
import org.springblade.core.datascope.props.DataScopeProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 数据权限配置类
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
@EnableConfigurationProperties(DataScopeProperties.class)
public class DataScopeConfiguration {
private final JdbcTemplate jdbcTemplate;
@Bean
@ConditionalOnMissingBean(ScopeModelHandler.class)
public ScopeModelHandler scopeModelHandler() {
return new BladeScopeModelHandler(jdbcTemplate);
}
@Bean
@ConditionalOnBean(ScopeModelHandler.class)
@ConditionalOnMissingBean(DataScopeHandler.class)
public DataScopeHandler dataScopeHandler(ScopeModelHandler scopeModelHandler) {
return new BladeDataScopeHandler(scopeModelHandler);
}
@Bean
@ConditionalOnBean(DataScopeHandler.class)
@ConditionalOnMissingBean(DataScopeInterceptor.class)
public DataScopeInterceptor interceptor(DataScopeHandler dataScopeHandler, DataScopeProperties dataScopeProperties) {
return new DataScopeInterceptor(dataScopeHandler, dataScopeProperties);
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.constant;
import org.springblade.core.tool.utils.StringUtil;
/**
* 数据权限常量
*
* @author Chill
*/
public interface DataScopeConstant {
String DEFAULT_COLUMN = "create_dept";
/**
* 获取部门数据
*/
String DATA_BY_DEPT = "select id from blade_dept where ancestors like concat(concat('%', ?),'%') and is_deleted = 0";
/**
* 根据resourceCode获取数据权限配置
*/
String DATA_BY_CODE = "select resource_code, scope_column, scope_field, scope_type, scope_value from blade_scope_data where resource_code = ?";
/**
* 根据mapperId获取数据权限配置
*
* @param size 数量
* @return String
*/
static String dataByMapper(int size) {
return "select resource_code, scope_column, scope_field, scope_type, scope_value from blade_scope_data where scope_class = ? and id in (select scope_id from blade_role_scope where role_id in (" + buildHolder(size) + "))";
}
/**
* 获取Sql占位符
*
* @param size 数量
* @return String
*/
static String buildHolder(int size) {
StringBuilder builder = StringUtil.builder();
for (int i = 0; i < size; i++) {
builder.append("?,");
}
return StringUtil.removeSuffix(builder.toString(), ",");
}
}

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据权限类型
*
* @author lengleng, Chill
*/
@Getter
@AllArgsConstructor
public enum DataScopeEnum {
/**
* 全部数据
*/
ALL(1, "全部"),
/**
* 本人可见
*/
OWN(2, "本人可见"),
/**
* 所在机构可见
*/
OWN_DEPT(3, "所在机构可见"),
/**
* 所在机构及子级可见
*/
OWN_DEPT_CHILD(4, "所在机构及子级可见"),
/**
* 自定义
*/
CUSTOM(5, "自定义");
/**
* 类型
*/
private final int type;
/**
* 描述
*/
private final String description;
public static DataScopeEnum of(Integer dataScopeType) {
if (dataScopeType == null) {
return null;
}
DataScopeEnum[] values = DataScopeEnum.values();
for (DataScopeEnum scopeTypeEnum : values) {
if (scopeTypeEnum.type == dataScopeType) {
return scopeTypeEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.exception;
/**
* 数据权限异常
*
* @author L.cm
*/
public class DataScopeException extends RuntimeException {
public DataScopeException() {
}
public DataScopeException(String message) {
super(message);
}
public DataScopeException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.handler;
import lombok.RequiredArgsConstructor;
import org.springblade.core.datascope.enums.DataScopeEnum;
import org.springblade.core.datascope.model.DataScopeModel;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.PlaceholderUtil;
import org.springblade.core.tool.utils.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 默认数据权限规则
*
* @author Chill
*/
@RequiredArgsConstructor
public class BladeDataScopeHandler implements DataScopeHandler {
private final ScopeModelHandler scopeModelHandler;
@Override
public String sqlCondition(String mapperId, DataScopeModel dataScope, BladeUser bladeUser, String originalSql) {
//数据权限资源编号
String code = dataScope.getResourceCode();
//根据mapperId从数据库中获取对应模型
DataScopeModel dataScopeDb = scopeModelHandler.getDataScopeByMapper(mapperId, bladeUser.getRoleId());
//mapperId配置未取到则从数据库中根据资源编号获取
if (dataScopeDb == null && StringUtil.isNotBlank(code)) {
dataScopeDb = scopeModelHandler.getDataScopeByCode(code);
}
//未从数据库找到对应配置则采用默认
dataScope = (dataScopeDb != null) ? dataScopeDb : dataScope;
//判断数据权限类型并组装对应Sql
Integer scopeRule = Objects.requireNonNull(dataScope).getScopeType();
DataScopeEnum scopeTypeEnum = DataScopeEnum.of(scopeRule);
List<Long> ids = new ArrayList<>();
String whereSql = "where scope.{} in ({})";
if (DataScopeEnum.ALL == scopeTypeEnum || StringUtil.containsAny(bladeUser.getRoleName(), RoleConstant.ADMIN)) {
return null;
} else if (DataScopeEnum.CUSTOM == scopeTypeEnum) {
whereSql = PlaceholderUtil.getDefaultResolver().resolveByMap(dataScope.getScopeValue(), BeanUtil.toMap(bladeUser));
} else if (DataScopeEnum.OWN == scopeTypeEnum) {
ids.add(bladeUser.getUserId());
} else if (DataScopeEnum.OWN_DEPT == scopeTypeEnum) {
ids.addAll(Func.toLongList(bladeUser.getDeptId()));
} else if (DataScopeEnum.OWN_DEPT_CHILD == scopeTypeEnum) {
List<Long> deptIds = Func.toLongList(bladeUser.getDeptId());
ids.addAll(deptIds);
deptIds.forEach(deptId -> {
List<Long> deptIdList = scopeModelHandler.getDeptAncestors(deptId);
ids.addAll(deptIdList);
});
}
return StringUtil.format("select {} from ({}) scope " + whereSql, Func.toStr(dataScope.getScopeField(), "*"), originalSql, dataScope.getScopeColumn(), StringUtil.join(ids));
}
}

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.handler;
import lombok.RequiredArgsConstructor;
import org.springblade.core.datascope.constant.DataScopeConstant;
import org.springblade.core.datascope.model.DataScopeModel;
import org.springblade.core.tool.utils.*;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.springblade.core.tool.utils.CacheUtil.SYS_CACHE;
/**
* BladeScopeModelHandler
*
* @author Chill
*/
@RequiredArgsConstructor
public class BladeScopeModelHandler implements ScopeModelHandler {
private static final String SCOPE_CACHE_CODE = "dataScope:code:";
private static final String SCOPE_CACHE_CLASS = "dataScope:class:";
private static final String DEPT_CACHE_ANCESTORS = "dept:ancestors:";
private static final DataScopeModel SEARCHED_DATA_SCOPE_MODEL = new DataScopeModel(Boolean.TRUE);
private final JdbcTemplate jdbcTemplate;
/**
* 获取数据权限
*
* @param mapperId 数据权限mapperId
* @param roleId 用户角色集合
* @return DataScopeModel
*/
@Override
public DataScopeModel getDataScopeByMapper(String mapperId, String roleId) {
List<Object> args = new ArrayList<>(Collections.singletonList(mapperId));
List<Long> roleIds = Func.toLongList(roleId);
args.addAll(roleIds);
// 增加searched字段防止未配置的参数重复读库导致缓存击穿
// 后续若有新增配置则会清空缓存重新加载
DataScopeModel dataScope = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CLASS, mapperId + StringPool.COLON + roleId, DataScopeModel.class);
if (dataScope == null || !dataScope.getSearched()) {
List<DataScopeModel> list = jdbcTemplate.query(DataScopeConstant.dataByMapper(roleIds.size()), args.toArray(), new BeanPropertyRowMapper<>(DataScopeModel.class));
if (CollectionUtil.isNotEmpty(list)) {
dataScope = list.iterator().next();
dataScope.setSearched(Boolean.TRUE);
} else {
dataScope = SEARCHED_DATA_SCOPE_MODEL;
}
CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CLASS, mapperId + StringPool.COLON + roleId, dataScope);
}
return StringUtil.isNotBlank(dataScope.getResourceCode()) ? dataScope : null;
}
/**
* 获取数据权限
*
* @param code 数据权限资源编号
* @return DataScopeModel
*/
@Override
public DataScopeModel getDataScopeByCode(String code) {
DataScopeModel dataScope = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, code, DataScopeModel.class);
// 增加searched字段防止未配置的参数重复读库导致缓存击穿
// 后续若有新增配置则会清空缓存重新加载
if (dataScope == null || !dataScope.getSearched()) {
List<DataScopeModel> list = jdbcTemplate.query(DataScopeConstant.DATA_BY_CODE, new Object[]{code}, new BeanPropertyRowMapper<>(DataScopeModel.class));
if (CollectionUtil.isNotEmpty(list)) {
dataScope = list.iterator().next();
dataScope.setSearched(Boolean.TRUE);
} else {
dataScope = SEARCHED_DATA_SCOPE_MODEL;
}
CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, code, dataScope);
}
return StringUtil.isNotBlank(dataScope.getResourceCode()) ? dataScope : null;
}
/**
* 获取部门子级
*
* @param deptId 部门id
* @return deptIds
*/
@Override
public List<Long> getDeptAncestors(Long deptId) {
List ancestors = CacheUtil.get(SYS_CACHE, DEPT_CACHE_ANCESTORS, deptId, List.class);
if (CollectionUtil.isEmpty(ancestors)) {
ancestors = jdbcTemplate.queryForList(DataScopeConstant.DATA_BY_DEPT, new Object[]{deptId}, Long.class);
CacheUtil.put(SYS_CACHE, DEPT_CACHE_ANCESTORS, deptId, ancestors);
}
return ancestors;
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.handler;
import org.springblade.core.datascope.model.DataScopeModel;
import org.springblade.core.secure.BladeUser;
/**
* 数据权限规则
*
* @author Chill
*/
public interface DataScopeHandler {
/**
* 获取过滤sql
*
* @param mapperId 数据查询类
* @param dataScope 数据权限类
* @param bladeUser 当前用户信息
* @param originalSql 原始Sql
* @return sql
*/
String sqlCondition(String mapperId, DataScopeModel dataScope, BladeUser bladeUser, String originalSql);
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.handler;
import org.springblade.core.datascope.model.DataScopeModel;
import java.util.List;
/**
* 获取数据权限模型统一接口
*
* @author Chill
*/
public interface ScopeModelHandler {
/**
* 获取数据权限
*
* @param mapperId 数据权限mapperId
* @param roleId 用户角色集合
* @return DataScopeModel
*/
DataScopeModel getDataScopeByMapper(String mapperId, String roleId);
/**
* 获取数据权限
*
* @param code 数据权限资源编号
* @return DataScopeModel
*/
DataScopeModel getDataScopeByCode(String code);
/**
* 获取部门子级
*
* @param deptId 部门id
* @return deptIds
*/
List<Long> getDeptAncestors(Long deptId);
}

View File

@ -0,0 +1,138 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.interceptor;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springblade.core.datascope.annotation.DataAuth;
import org.springblade.core.datascope.handler.DataScopeHandler;
import org.springblade.core.datascope.model.DataScopeModel;
import org.springblade.core.datascope.props.DataScopeProperties;
import org.springblade.core.mp.intercept.QueryInterceptor;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringUtil;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* mybatis 数据权限拦截器
*
* @author L.cm, Chill
*/
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings({"rawtypes"})
public class DataScopeInterceptor implements QueryInterceptor {
private final ConcurrentMap<String, DataAuth> dataAuthMap = new ConcurrentHashMap<>(8);
private final DataScopeHandler dataScopeHandler;
private final DataScopeProperties dataScopeProperties;
@Override
public void intercept(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//未启用则放行
if (!dataScopeProperties.getEnabled()) {
return;
}
//未取到用户则放行
BladeUser bladeUser = SecureUtil.getUser();
if (bladeUser == null) {
return;
}
if (SqlCommandType.SELECT != ms.getSqlCommandType() || StatementType.CALLABLE == ms.getStatementType()) {
return;
}
String originalSql = boundSql.getSql();
//查找注解中包含DataAuth类型的参数
DataAuth dataAuth = findDataAuthAnnotation(ms);
//注解为空并且数据权限方法名未匹配到,则放行
String mapperId = ms.getId();
String className = mapperId.substring(0, mapperId.lastIndexOf(StringPool.DOT));
String mapperName = ClassUtil.getShortName(className);
String methodName = mapperId.substring(mapperId.lastIndexOf(StringPool.DOT) + 1);
boolean mapperSkip = dataScopeProperties.getMapperKey().stream().noneMatch(methodName::contains)
|| dataScopeProperties.getMapperExclude().stream().anyMatch(mapperName::contains);
if (dataAuth == null && mapperSkip) {
return;
}
//创建数据权限模型
DataScopeModel dataScope = new DataScopeModel();
//若注解不为空,则配置注解项
if (dataAuth != null) {
dataScope.setResourceCode(dataAuth.code());
dataScope.setScopeColumn(dataAuth.column());
dataScope.setScopeType(dataAuth.type().getType());
dataScope.setScopeField(dataAuth.field());
dataScope.setScopeValue(dataAuth.value());
}
//获取数据权限规则对应的筛选Sql
String sqlCondition = dataScopeHandler.sqlCondition(mapperId, dataScope, bladeUser, originalSql);
if (!StringUtil.isBlank(sqlCondition)) {
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
mpBoundSql.sql(sqlCondition);
}
}
/**
* 获取数据权限注解信息
*
* @param mappedStatement mappedStatement
* @return DataAuth
*/
private DataAuth findDataAuthAnnotation(MappedStatement mappedStatement) {
String id = mappedStatement.getId();
return dataAuthMap.computeIfAbsent(id, (key) -> {
String className = key.substring(0, key.lastIndexOf(StringPool.DOT));
String mapperBean = StringUtil.firstCharToLower(ClassUtil.getShortName(className));
Object mapper = SpringUtil.getBean(mapperBean);
String methodName = key.substring(key.lastIndexOf(StringPool.DOT) + 1);
Class<?>[] interfaces = ClassUtil.getAllInterfaces(mapper);
for (Class<?> mapperInterface : interfaces) {
for (Method method : mapperInterface.getDeclaredMethods()) {
if (methodName.equals(method.getName()) && method.isAnnotationPresent(DataAuth.class)) {
return method.getAnnotation(DataAuth.class);
}
}
}
return null;
});
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springblade.core.datascope.constant.DataScopeConstant;
import org.springblade.core.datascope.enums.DataScopeEnum;
import java.io.Serializable;
/**
* 数据权限实体类
*
* @author Chill
*/
@Data
@NoArgsConstructor
public class DataScopeModel implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 构造器创建
*/
public DataScopeModel(Boolean searched) {
this.searched = searched;
}
/**
* 是否已查询
*/
private Boolean searched = Boolean.FALSE;
/**
* 资源编号
*/
private String resourceCode;
/**
* 数据权限字段
*/
private String scopeColumn = DataScopeConstant.DEFAULT_COLUMN;
/**
* 数据权限规则
*/
private Integer scopeType = DataScopeEnum.ALL.getType();
/**
* 可见字段
*/
private String scopeField;
/**
* 数据权限规则值
*/
private String scopeValue;
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.datascope.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 数据权限参数配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties(prefix = "blade.data-scope")
public class DataScopeProperties {
/**
* 开启数据权限
*/
private Boolean enabled = true;
/**
* mapper方法匹配关键字
*/
private List<String> mapperKey = Arrays.asList("page", "Page", "list", "List");
/**
* mapper过滤
*/
private List<String> mapperExclude = Collections.singletonList("FlowMapper");
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -25,7 +25,7 @@ public interface AppConstant {
/**
* 应用版本
*/
String APPLICATION_VERSION = "3.1.0";
String APPLICATION_VERSION = "3.2.0";
/**
* 基础包

View File

@ -33,6 +33,7 @@ public interface TokenConstant {
String ACCOUNT = "account";
String USER_ID = "user_id";
String ROLE_ID = "role_id";
String DEPT_ID = "dept_id";
String USER_NAME = "user_name";
String ROLE_NAME = "role_name";
String TENANT_ID = "tenant_id";

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -13,21 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.boot.config;
package org.springblade.core.mp.config;
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.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import org.mybatis.spring.annotation.MapperScan;
import org.springblade.core.boot.props.MybatisPlusProperties;
import org.springblade.core.mp.intercept.QueryInterceptor;
import org.springblade.core.mp.plugins.BladePaginationInterceptor;
import org.springblade.core.mp.plugins.SqlLogInterceptor;
import org.springblade.core.mp.props.MybatisPlusProperties;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* mybatisplus 配置
@ -40,22 +49,48 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties(MybatisPlusProperties.class)
public class MybatisPlusConfiguration {
private final TenantLineHandler tenantLineHandler;
/**
* 租户拦截器
*/
@Bean
@ConditionalOnMissingBean(TenantLineInnerInterceptor.class)
public TenantLineInnerInterceptor tenantLineInnerInterceptor() {
return new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
return new StringValue(Func.toStr(SecureUtil.getTenantId(), BladeConstant.ADMIN_TENANT_ID));
}
@Override
public boolean ignoreTable(String tableName) {
return true;
}
});
}
/**
* mybatis-plus 拦截器集合
*/
@Bean
@ConditionalOnMissingBean(MybatisPlusInterceptor.class)
public MybatisPlusInterceptor mybatisPlusInterceptor(MybatisPlusProperties mybatisPlusProperties) {
public MybatisPlusInterceptor mybatisPlusInterceptor(ObjectProvider<QueryInterceptor[]> queryInterceptors,
TenantLineInnerInterceptor tenantLineInnerInterceptor,
MybatisPlusProperties mybatisPlusProperties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 配置租户拦截器
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
// 配置分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setMaxLimit(mybatisPlusProperties.getPageLimit());
paginationInnerInterceptor.setOverflow(mybatisPlusProperties.getOverflow());
interceptor.addInnerInterceptor(paginationInnerInterceptor);
BladePaginationInterceptor paginationInterceptor = new BladePaginationInterceptor();
// 配置自定义查询拦截器
QueryInterceptor[] queryInterceptorArray = queryInterceptors.getIfAvailable();
if (ObjectUtil.isNotEmpty(queryInterceptorArray)) {
AnnotationAwareOrderComparator.sort(queryInterceptorArray);
paginationInterceptor.setQueryInterceptors(queryInterceptorArray);
}
paginationInterceptor.setMaxLimit(mybatisPlusProperties.getPageLimit());
paginationInterceptor.setOverflow(mybatisPlusProperties.getOverflow());
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2018-2028, DreamLu All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: DreamLu 卢春梦 (596392912@qq.com)
*/
package org.springblade.core.mp.intercept;
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.core.Ordered;
/**
* 自定义 mybatis plus 查询拦截器
*
* @author L.cm
*/
@SuppressWarnings({"rawtypes"})
public interface QueryInterceptor extends Ordered {
/**
* 拦截处理
*
* @param executor
* @param ms
* @param parameter
* @param rowBounds
* @param resultHandler
* @param boundSql
*/
void intercept(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql);
/**
* 排序
*
* @return int
*/
@Override
default int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.mp.plugins;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.Setter;
import lombok.SneakyThrows;
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.springblade.core.mp.intercept.QueryInterceptor;
/**
* 拓展分页拦截器
*
* @author Chill
*/
@Setter
public class BladePaginationInterceptor extends PaginationInnerInterceptor {
/**
* 查询拦截器
*/
private QueryInterceptor[] queryInterceptors;
@SneakyThrows
@Override
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
QueryInterceptorExecutor.exec(queryInterceptors, executor, ms, parameter, rowBounds, resultHandler, boundSql);
return super.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.mp.plugins;
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.springblade.core.mp.intercept.QueryInterceptor;
import org.springblade.core.tool.utils.ObjectUtil;
/**
* 查询拦截器执行器
*
* <p>
* 目的抽取此方法是为了后期方便同步更新 {@link BladePaginationInterceptor}
* </p>
*
* @author L.cm
*/
@SuppressWarnings({"rawtypes"})
public class QueryInterceptorExecutor {
/**
* 执行查询拦截器
*/
static void exec(QueryInterceptor[] interceptors, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws Throwable {
if (ObjectUtil.isEmpty(interceptors)) {
return;
}
for (QueryInterceptor interceptor : interceptors) {
interceptor.intercept(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.boot.props;
package org.springblade.core.mp.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -45,6 +45,11 @@ public class BladeUser implements Serializable {
*/
@ApiModelProperty(hidden = true)
private String tenantId;
/**
* 部门id
*/
@ApiModelProperty(hidden = true)
private String deptId;
/**
* 昵称
*/

View File

@ -48,6 +48,7 @@ public class SecureUtil {
private final static String ACCOUNT = TokenConstant.ACCOUNT;
private final static String USER_ID = TokenConstant.USER_ID;
private final static String ROLE_ID = TokenConstant.ROLE_ID;
private final static String DEPT_ID = TokenConstant.DEPT_ID;
private final static String USER_NAME = TokenConstant.USER_NAME;
private final static String ROLE_NAME = TokenConstant.ROLE_NAME;
private final static String TENANT_ID = TokenConstant.TENANT_ID;
@ -98,6 +99,7 @@ public class SecureUtil {
Long userId = Func.toLong(claims.get(SecureUtil.USER_ID));
String tenantId = Func.toStr(claims.get(SecureUtil.TENANT_ID));
String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID));
String deptId = Func.toStr(claims.get(SecureUtil.DEPT_ID));
String account = Func.toStr(claims.get(SecureUtil.ACCOUNT));
String roleName = Func.toStr(claims.get(SecureUtil.ROLE_NAME));
String userName = Func.toStr(claims.get(SecureUtil.USER_NAME));
@ -107,6 +109,7 @@ public class SecureUtil {
bladeUser.setTenantId(tenantId);
bladeUser.setAccount(account);
bladeUser.setRoleId(roleId);
bladeUser.setDeptId(deptId);
bladeUser.setRoleName(roleName);
bladeUser.setUserName(userName);
return bladeUser;

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -55,7 +55,7 @@ public class SwaggerProperties {
/**
* 版本
**/
private String version = "3.1.0";
private String version = "3.2.0";
/**
* 许可证
**/

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -44,6 +44,8 @@ public class XssConfiguration {
/**
* 防XSS注入
*
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {

View File

@ -65,6 +65,11 @@ public interface BladeConstant {
int DB_ADMIN_NON_LOCKED = 0;
int DB_ADMIN_LOCKED = 1;
/**
* 顶级父节点id
*/
Long TOP_PARENT_ID = 0L;
/**
* 管理员对应的租户ID
*/

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: Chill 庄骞 (smallchill@163.com)
*/
package org.springblade.core.tool.utils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.lang.Nullable;
import java.util.concurrent.Callable;
/**
* 缓存工具类
*
* @author Chill
*/
public class CacheUtil {
public static final String SYS_CACHE = "blade:sys";
private static CacheManager cacheManager;
/**
* 获取缓存工具
*
* @return CacheManager
*/
private static CacheManager getCacheManager() {
if (cacheManager == null) {
cacheManager = SpringUtil.getBean(CacheManager.class);
}
return cacheManager;
}
/**
* 获取缓存对象
*
* @param cacheName 缓存名
* @return Cache
*/
public static Cache getCache(String cacheName) {
return getCacheManager().getCache(cacheName);
}
/**
* 获取缓存
*
* @param cacheName 缓存名
* @param keyPrefix 缓存键前缀
* @param key 缓存键值
* @return Cache
*/
@Nullable
public static Object get(String cacheName, String keyPrefix, Object key) {
if (Func.hasEmpty(cacheName, keyPrefix, key)) {
return null;
}
return getCache(cacheName).get(keyPrefix.concat(String.valueOf(key))).get();
}
/**
* 获取缓存
*
* @param cacheName 缓存名
* @param keyPrefix 缓存键前缀
* @param key 缓存键值
* @param type 转换类型
* @param <T> 类型
* @return Cache
*/
@Nullable
public static <T> T get(String cacheName, String keyPrefix, Object key, @Nullable Class<T> type) {
if (Func.hasEmpty(cacheName, keyPrefix, key)) {
return null;
}
return getCache(cacheName).get(keyPrefix.concat(String.valueOf(key)), type);
}
/**
* 获取缓存
*
* @param cacheName 缓存名
* @param keyPrefix 缓存键前缀
* @param key 缓存键值
* @param valueLoader 重载对象
* @param <T> 类型
* @return Cache
*/
@Nullable
public static <T> T get(String cacheName, String keyPrefix, Object key, Callable<T> valueLoader) {
if (Func.hasEmpty(cacheName, keyPrefix, key)) {
return null;
}
try {
Cache.ValueWrapper valueWrapper = getCache(cacheName).get(keyPrefix.concat(String.valueOf(key)));
Object value = null;
if (valueWrapper == null) {
T call = valueLoader.call();
if (Func.isNotEmpty(call)) {
getCache(cacheName).put(keyPrefix.concat(String.valueOf(key)), call);
value = call;
}
} else {
value = valueWrapper.get();
}
return (T) value;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
* 设置缓存
*
* @param cacheName 缓存名
* @param keyPrefix 缓存键前缀
* @param key 缓存键值
* @param value 缓存值
*/
public static void put(String cacheName, String keyPrefix, Object key, @Nullable Object value) {
getCache(cacheName).put(keyPrefix.concat(String.valueOf(key)), value);
}
/**
* 清除缓存
*
* @param cacheName 缓存名
* @param keyPrefix 缓存键前缀
* @param key 缓存键值
*/
public static void evict(String cacheName, String keyPrefix, Object key) {
if (Func.hasEmpty(cacheName, keyPrefix, key)) {
return;
}
getCache(cacheName).evict(keyPrefix.concat(String.valueOf(key)));
}
/**
* 清空缓存
*
* @param cacheName 缓存名
*/
public static void clear(String cacheName) {
if (Func.isEmpty(cacheName)) {
return;
}
getCache(cacheName).clear();
}
}

View File

@ -405,6 +405,7 @@ public class DateUtil {
*
* @param dateStr 时间字符串
* @param pattern 表达式
* @param query 移动查询
* @return 时间
*/
public static <T> T parse(String dateStr, String pattern, TemporalQuery<T> query) {
@ -453,6 +454,9 @@ public class DateUtil {
/**
* Converts local date time to Calendar.
*
* @param localDateTime LocalDateTime
* @return Calendar
*/
public static Calendar toCalendar(final LocalDateTime localDateTime) {
return GregorianCalendar.from(ZonedDateTime.of(localDateTime, ZoneId.systemDefault()));

View File

@ -0,0 +1,143 @@
package org.springblade.core.tool.utils;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* 占位符解析器
*
* @author meilin.huang, chill
*/
public class PlaceholderUtil {
/**
* 默认前缀占位符
*/
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
/**
* 默认后缀占位符
*/
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
/**
* 默认单例解析器
*/
private static final PlaceholderUtil DEFAULT_RESOLVER = new PlaceholderUtil();
/**
* 占位符前缀
*/
private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
/**
* 占位符后缀
*/
private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
private PlaceholderUtil() {
}
private PlaceholderUtil(String placeholderPrefix, String placeholderSuffix) {
this.placeholderPrefix = placeholderPrefix;
this.placeholderSuffix = placeholderSuffix;
}
/**
* 获取默认的占位符解析器即占位符前缀为"${", 后缀为"}"
*
* @return PlaceholderUtil
*/
public static PlaceholderUtil getDefaultResolver() {
return DEFAULT_RESOLVER;
}
public static PlaceholderUtil getResolver(String placeholderPrefix, String placeholderSuffix) {
return new PlaceholderUtil(placeholderPrefix, placeholderSuffix);
}
/**
* 解析带有指定占位符的模板字符串默认占位符为前缀${ 后缀}
*
* @param content 要解析的带有占位符的模板字符串
* @param values 按照模板占位符索引位置设置对应的值
* @return {String}
*/
public String resolve(String content, String... values) {
int start = content.indexOf(this.placeholderPrefix);
if (start == -1) {
return content;
}
//值索引
int valueIndex = 0;
StringBuilder result = new StringBuilder(content);
while (start != -1) {
int end = result.indexOf(this.placeholderSuffix);
String replaceContent = values[valueIndex++];
result.replace(start, end + this.placeholderSuffix.length(), replaceContent);
start = result.indexOf(this.placeholderPrefix, start + replaceContent.length());
}
return result.toString();
}
/**
* 解析带有指定占位符的模板字符串默认占位符为前缀${ 后缀}
*
* @param content 要解析的带有占位符的模板字符串
* @param values 按照模板占位符索引位置设置对应的值
* @return {String}
*/
public String resolve(String content, Object[] values) {
return resolve(content, Stream.of(values).map(String::valueOf).toArray(String[]::new));
}
/**
* 根据替换规则来替换指定模板中的占位符值
*
* @param content 要解析的字符串
* @param rule 解析规则回调
* @return {String}
*/
public String resolveByRule(String content, Function<String, String> rule) {
int start = content.indexOf(this.placeholderPrefix);
if (start == -1) {
return content;
}
StringBuilder result = new StringBuilder(content);
while (start != -1) {
int end = result.indexOf(this.placeholderSuffix, start + 1);
//获取占位符属性值如${id}, 即获取id
String placeholder = result.substring(start + this.placeholderPrefix.length(), end);
//替换整个占位符内容即将${id}值替换为替换规则回调中的内容
String replaceContent = placeholder.trim().isEmpty() ? "" : rule.apply(placeholder);
result.replace(start, end + this.placeholderSuffix.length(), replaceContent);
start = result.indexOf(this.placeholderPrefix, start + replaceContent.length());
}
return result.toString();
}
/**
* 替换模板中占位符内容占位符的内容即为map key对应的值key为占位符中的内容
*
* @param content 模板内容
* @param valueMap 值映射
* @return 替换完成后的字符串
*/
public String resolveByMap(String content, final Map<String, Object> valueMap) {
return resolveByRule(content, placeholderValue -> String.valueOf(valueMap.get(placeholderValue)));
}
/**
* 根据properties文件替换占位符内容
*
* @param content 模板内容
* @param properties 配置
* @return {String}
*/
public String resolveByProperties(String content, final Properties properties) {
return resolveByRule(content, properties::getProperty);
}
}

View File

@ -1438,5 +1438,40 @@ public class StringUtil extends org.springframework.util.StringUtils {
return sb.toString().toLowerCase();
}
/**
* 首字母变小写
*
* @param str 字符串
* @return {String}
*/
public static String firstCharToLower(String str) {
char firstChar = str.charAt(0);
if (firstChar >= CharPool.UPPER_A && firstChar <= CharPool.UPPER_Z) {
char[] arr = str.toCharArray();
arr[0] += (CharPool.LOWER_A - CharPool.UPPER_A);
return new String(arr);
}
return str;
}
/**
* 首字母变大写
*
* @param str 字符串
* @return {String}
*/
public static String firstCharToUpper(String str) {
char firstChar = str.charAt(0);
if (firstChar >= CharPool.LOWER_A && firstChar <= CharPool.LOWER_Z) {
char[] arr = str.toCharArray();
arr[0] -= (CharPool.LOWER_A - CharPool.UPPER_A);
return new String(arr);
}
return str;
}
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

23
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<packaging>pom</packaging>
<name>blade-tool</name>
<description>
@ -36,26 +36,26 @@
</scm>
<properties>
<blade.tool.version>3.1.0</blade.tool.version>
<blade.tool.version>3.2.0</blade.tool.version>
<java.version>1.8</java.version>
<maven.plugin.version>3.8.0</maven.plugin.version>
<maven.plugin.version>3.8.1</maven.plugin.version>
<swagger.version>2.10.5</swagger.version>
<swagger.models.version>1.6.2</swagger.models.version>
<knife4j.version>2.0.8</knife4j.version>
<mybatis.plus.version>3.4.3.1</mybatis.plus.version>
<knife4j.version>2.0.9</knife4j.version>
<mybatis.plus.version>3.4.3.4</mybatis.plus.version>
<mybatis.plus.generator.version>3.4.1</mybatis.plus.generator.version>
<protostuff.version>1.6.0</protostuff.version>
<disruptor.version>3.4.2</disruptor.version>
<spring.boot.admin.version>2.3.1</spring.boot.admin.version>
<spring.boot.admin.version>2.5.3</spring.boot.admin.version>
<mica.auto.version>1.2.5</mica.auto.version>
<alibaba.cloud.version>2021.1</alibaba.cloud.version>
<alibaba.nacos.version>2.0.2</alibaba.nacos.version>
<alibaba.nacos.version>2.0.3</alibaba.nacos.version>
<alibaba.seata.version>1.4.2</alibaba.seata.version>
<spring.plugin.version>2.0.0.RELEASE</spring.plugin.version>
<spring.boot.version>2.5.2</spring.boot.version>
<spring.cloud.version>2020.0.3</spring.cloud.version>
<spring.boot.version>2.5.6</spring.boot.version>
<spring.cloud.version>2020.0.4</spring.cloud.version>
<spring.platform.version>Cairo-SR8</spring.platform.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -77,6 +77,7 @@
<module>blade-core-oss</module>
<module>blade-core-transaction</module>
<module>blade-core-report</module>
<module>blade-core-datascope</module>
</modules>
<dependencyManagement>
@ -278,7 +279,7 @@
</executions>
</plugin>
<!-- Nexus -->
<plugin>
<!--<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
@ -288,7 +289,7 @@
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugin>-->
</plugins>
</build>
<distributionManagement>