Compare commits

..

12 Commits

40 changed files with 737 additions and 371 deletions

View File

@@ -1,3 +1,19 @@
## [v2.7.2](https://github.com/continew-org/continew-starter/compare/v2.7.1...v2.7.2) (2024-11-12)
### ✨ 新特性
- 【extension/crud】支持树结构全局配置 ([5891c4a](https://github.com/continew-org/continew-starter/commit/5891c4aa61b14ba11a387a478fb3616dfc52217c))
- 【extension/crud】查询字典列表新增支持 extraKeys 额外信息字段 ([9b7ea33](https://github.com/continew-org/continew-starter/commit/9b7ea33c0b6714e2ea631aa26f0650e78857079a)) ([a8c6ea3](https://github.com/continew-org/continew-starter/commit/a8c6ea30797811d885f294f28eb95afb935ad7b4))
- 【cache/redisson】RedisUtils 新增上锁、释放锁方法 ([04498ff](https://github.com/continew-org/continew-starter/commit/04498ffe56b062bce1200292b23d2c31341771e6))
### 💎 功能优化
- 【extension/crud】移除 TreeUtils ([f4b2310](https://github.com/continew-org/continew-starter/commit/f4b23102a9a31b2120f40a8288bc0aedc36e11b4))
- 【data/mp】移除冗余的数据库类型判空处理 ([bd60411](https://github.com/continew-org/continew-starter/commit/bd60411f3e4fa87c26e492df96fbfb088ea3ce85))
- 【core】重构 IP 工具类获取归属地的返回格式(更方便数据处理) ([e9b9d8b](https://github.com/continew-org/continew-starter/commit/e9b9d8b82e7e28be82c9ed518582d88f507cfac2))
- 【data】Query 范围查询支持数组数据 ([673e586](https://github.com/continew-org/continew-starter/commit/673e586aafc8578f0c7ab063ca9df9b1265f88d5))
- 【data】重构 MetaUtils 获取表信息方法 ([1ce5eb3](https://github.com/continew-org/continew-starter/commit/1ce5eb3b734b13ccd47e3848117daf3c2d7d0afa))
## [v2.7.0](https://github.com/continew-org/continew-starter/compare/v2.6.0...v2.7.0) (2024-09-28)
### ✨ 新特性

View File

@@ -24,6 +24,8 @@ import top.continew.starter.core.constant.StringConstants;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Redis 工具类
@@ -207,6 +209,80 @@ public class RedisUtils {
return rateLimiter.tryAcquire(1);
}
/**
* 尝试获取锁
*
* @param key 键
* @param expireTime 锁过期时间(单位:毫秒)
* @param timeout 获取锁超时时间(单位:毫秒)
* @return true成功false失败
* @since 2.7.2
*/
public static boolean tryLock(String key, long expireTime, long timeout) {
return tryLock(key, expireTime, timeout, TimeUnit.MILLISECONDS);
}
/**
* 释放锁
*
* @param key 键
* @return true释放成功false释放失败
* @since 2.7.2
*/
public static boolean unlock(String key) {
RLock lock = getLock(key);
return unlock(lock);
}
/**
* 尝试获取锁
*
* @param key 键
* @param expireTime 锁过期时间
* @param timeout 获取锁超时时间
* @param unit 时间单位
* @return true成功false失败
* @since 2.7.2
*/
public static boolean tryLock(String key, long expireTime, long timeout, TimeUnit unit) {
RLock lock = getLock(key);
try {
return lock.tryLock(timeout, expireTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* 释放锁
*
* @param lock 锁实例
* @return true释放成功false释放失败
* @since 2.7.2
*/
public static boolean unlock(RLock lock) {
if (lock.isHeldByCurrentThread()) {
try {
lock.unlockAsync().get();
return true;
} catch (ExecutionException | InterruptedException e) {
return false;
}
}
return false;
}
/**
* 获取锁实例
*
* @param key 键
* @return 锁实例
* @since 2.7.2
*/
public static RLock getLock(String key) {
return CLIENT.getLock(key);
}
/**
* 格式化键,将各子键用 : 拼接起来
*

View File

@@ -119,6 +119,11 @@ public class PropertiesConstants {
*/
public static final String MESSAGING_WEBSOCKET = MESSAGING + StringConstants.DOT + "websocket";
/**
* CRUD 配置
*/
public static final String CRUD = CONTINEW_STARTER + StringConstants.DOT + "crud";
/**
* 数据权限配置
*/

View File

@@ -254,6 +254,11 @@ public class StringConstants {
*/
public static final String QUESTION_MARK = "?";
/**
* 管道符
*/
public static final String PIPE = "|";
/**
* 中文逗号
*/

View File

@@ -18,13 +18,13 @@ package top.continew.starter.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HtmlUtil;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import net.dreamlu.mica.ip2region.core.IpInfo;
import top.continew.starter.core.constant.StringConstants;
import java.util.Objects;
import java.util.Set;
/**
@@ -50,12 +50,13 @@ public class IpUtils {
}
Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class);
IpInfo ipInfo = ip2regionSearcher.memorySearch(ip);
if (null != ipInfo) {
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getAddress(), ipInfo.getIsp());
regionSet.removeIf(CharSequenceUtil::isBlank);
return String.join(StringConstants.SPACE, regionSet);
if (null == ipInfo) {
return null;
}
return null;
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getCountry(), ipInfo.getRegion(), ipInfo
.getProvince(), ipInfo.getCity(), ipInfo.getIsp());
regionSet.removeIf(Objects::isNull);
return String.join(StringConstants.PIPE, regionSet);
}
/**

View File

@@ -16,18 +16,21 @@
package top.continew.starter.data.core.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.DbUtil;
import cn.hutool.db.meta.Column;
import cn.hutool.db.meta.MetaUtil;
import cn.hutool.db.meta.Table;
import cn.hutool.db.meta.TableType;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.data.core.enums.DatabaseType;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
@@ -80,7 +83,7 @@ public class MetaUtils {
* @param dataSource 数据源
* @return 表信息列表
*/
public static List<Table> getTables(DataSource dataSource) throws SQLException {
public static List<Table> getTables(DataSource dataSource) {
return getTables(dataSource, null);
}
@@ -90,27 +93,39 @@ public class MetaUtils {
* @param dataSource 数据源
* @param tableName 表名称
* @return 表信息列表
* @author looly
* @since 2.7.2
*/
public static List<Table> getTables(DataSource dataSource, String tableName) throws SQLException {
String querySql = "SHOW TABLE STATUS";
List<Entity> tableEntityList;
Db db = Db.use(dataSource);
if (CharSequenceUtil.isNotBlank(tableName)) {
tableEntityList = db.query("%s WHERE NAME = ?".formatted(querySql), tableName);
} else {
tableEntityList = db.query(querySql);
public static List<Table> getTables(DataSource dataSource, String tableName) {
List<Table> tables = new ArrayList<>();
Connection conn = null;
try {
conn = dataSource.getConnection();
String catalog = MetaUtil.getCatalog(conn);
String schema = MetaUtil.getSchema(conn);
final DatabaseMetaData metaData = conn.getMetaData();
try (final ResultSet rs = metaData.getTables(catalog, schema, tableName, Convert
.toStrArray(TableType.TABLE))) {
if (null != rs) {
String name;
while (rs.next()) {
name = rs.getString("TABLE_NAME");
if (CharSequenceUtil.isNotBlank(name)) {
final Table table = Table.create(name);
table.setCatalog(catalog);
table.setSchema(schema);
table.setComment(MetaUtil.getRemarks(metaData, catalog, schema, name));
tables.add(table);
}
}
}
}
return tables;
} catch (Exception e) {
throw new DbRuntimeException("Get tables error!", e);
} finally {
DbUtil.close(conn);
}
List<Table> tableList = new ArrayList<>(tableEntityList.size());
for (Entity tableEntity : tableEntityList) {
Table table = new Table(tableEntity.getStr("NAME"));
table.setComment(tableEntity.getStr("COMMENT"));
table.setEngine(tableEntity.getStr("ENGINE"));
table.setCharset(tableEntity.getStr("COLLATION"));
table.setCreateTime(DateUtil.toLocalDateTime(tableEntity.getDate("CREATE_TIME")));
table.setUpdateTime(DateUtil.toLocalDateTime(tableEntity.getDate("UPDATE_TIME")));
tableList.add(table);
}
return tableList;
}
/**
@@ -121,7 +136,7 @@ public class MetaUtils {
* @return 列信息列表
*/
public static Collection<Column> getColumns(DataSource dataSource, String tableName) {
cn.hutool.db.meta.Table table = MetaUtil.getTableMeta(dataSource, tableName);
Table table = MetaUtil.getTableMeta(dataSource, tableName);
return table.getColumns();
}
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.data.core.util;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 数据库表信息
*
* @author Charles7c
* @since 1.0.0
*/
public class Table implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 表名称
*/
private String tableName;
/**
* 注释
*/
private String comment;
/**
* 存储引擎
*/
private String engine;
/**
* 字符集
*/
private String charset;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 修改时间
*/
private LocalDateTime updateTime;
public Table(String tableName) {
this.tableName = tableName;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}

View File

@@ -199,7 +199,9 @@ public class QueryWrapperHelper {
case LT -> consumers.add(q -> q.lt(columnName, fieldValue));
case LE -> consumers.add(q -> q.le(columnName, fieldValue));
case BETWEEN -> {
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
List<Object> between = new ArrayList<>(ArrayUtil.isArray(fieldValue)
? CollUtil.toList(fieldValue)
: (List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
consumers.add(q -> q.between(columnName, between.get(0), between.get(1)));
}
@@ -208,11 +210,15 @@ public class QueryWrapperHelper {
case LIKE_RIGHT -> consumers.add(q -> q.likeRight(columnName, fieldValue));
case IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.in(columnName, (Collection<Object>)fieldValue));
consumers.add(q -> q.in(columnName, ArrayUtil.isArray(fieldValue)
? CollUtil.toList(fieldValue)
: (Collection<Object>)fieldValue));
}
case NOT_IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.notIn(columnName, (Collection<Object>)fieldValue));
consumers.add(q -> q.notIn(columnName, ArrayUtil.isArray(fieldValue)
? CollUtil.toList(fieldValue)
: (Collection<Object>)fieldValue));
}
case IS_NULL -> consumers.add(q -> q.isNull(columnName));
case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName));

View File

@@ -107,9 +107,8 @@ public class MybatisPlusAutoConfiguration {
*/
private PaginationInnerInterceptor paginationInnerInterceptor(MyBatisPlusExtensionProperties.PaginationProperties paginationProperties) {
// 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
PaginationInnerInterceptor paginationInnerInterceptor = null != paginationProperties.getDbType()
? new PaginationInnerInterceptor(paginationProperties.getDbType())
: new PaginationInnerInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(paginationProperties
.getDbType());
paginationInnerInterceptor.setOverflow(paginationProperties.isOverflow());
paginationInnerInterceptor.setMaxLimit(paginationProperties.getMaxLimit());
return paginationInnerInterceptor;

View File

@@ -193,7 +193,9 @@ public class QueryWrapperHelper {
case LT -> consumers.add(q -> q.lt(columnName, fieldValue));
case LE -> consumers.add(q -> q.le(columnName, fieldValue));
case BETWEEN -> {
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
List<Object> between = new ArrayList<>(ArrayUtil.isArray(fieldValue)
? CollUtil.toList(fieldValue)
: (List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
consumers.add(q -> q.between(columnName, between.get(0), between.get(1)));
}
@@ -202,11 +204,15 @@ public class QueryWrapperHelper {
case LIKE_RIGHT -> consumers.add(q -> q.likeRight(columnName, fieldValue));
case IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.in(columnName, (Collection<Object>)fieldValue));
consumers.add(q -> q.in(columnName, ArrayUtil.isArray(fieldValue)
? CollUtil.toList(fieldValue)
: (Collection<Object>)fieldValue));
}
case NOT_IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.notIn(columnName, (Collection<Object>)fieldValue));
consumers.add(q -> q.notIn(columnName, ArrayUtil.isArray(fieldValue)
? CollUtil.toList(fieldValue)
: (Collection<Object>)fieldValue));
}
case IS_NULL -> consumers.add(q -> q.isNull(columnName));
case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName));

View File

@@ -43,7 +43,7 @@
<properties>
<!-- 项目版本号 -->
<revision>2.7.0</revision>
<revision>2.7.2</revision>
<snail-job.version>1.1.2</snail-job.version>
<sa-token.version>1.39.0</sa-token.version>
<just-auth.version>1.16.6</just-auth.version>

View File

@@ -42,4 +42,11 @@ public @interface DictField {
* @return 值字段名
*/
String valueKey() default "id";
/**
* 额外信息字段名
*
* @return 额外信息字段名
*/
String[] extraKeys() default {};
}

View File

@@ -71,4 +71,11 @@ public @interface TreeField {
* @return 递归深度
*/
int deep() default -1;
/**
* 根节点 ID
*
* @return 根节点 ID
*/
long rootId() default 0L;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.crud.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* CRUD 配置属性
*
* @author Charles7c
* @since 2.7.2
*/
@ConfigurationProperties(PropertiesConstants.CRUD)
public class CrudProperties {
/**
* 树配置
*/
@NestedConfigurationProperty
private CrudTreeProperties tree = new CrudTreeProperties();
public CrudTreeProperties getTree() {
return tree;
}
public void setTree(CrudTreeProperties tree) {
this.tree = tree;
}
}

View File

@@ -20,6 +20,7 @@ import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
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,6 +37,7 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
* @since 1.0.0
*/
@Configuration
@EnableConfigurationProperties(CrudProperties.class)
public class CrudRestControllerAutoConfiguration extends DelegatingWebMvcConfiguration {
private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class);

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.crud.autoconfigure;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.annotation.TreeField;
/**
* CRUD 树列表配置属性
*
* @author Charles7c
* @since 2.7.2
*/
public class CrudTreeProperties {
/**
* ID 字段名
*/
private String idKey = "id";
/**
* 父 ID 字段名
*
*/
private String parentIdKey = "parentId";
/**
* 名称字段名
*
*/
private String nameKey = "name";
/**
* 排序字段名
*
*/
private String weightKey = "weight";
/**
* 子列表字段名
*
*/
private String childrenKey = "children";
/**
* 递归深度(< 0 不限制)
*/
private Integer deep = -1;
/**
* 根节点 ID
*/
private Long rootId = 0L;
public String getIdKey() {
return idKey;
}
public void setIdKey(String idKey) {
this.idKey = idKey;
}
public String getParentIdKey() {
return parentIdKey;
}
public void setParentIdKey(String parentIdKey) {
this.parentIdKey = parentIdKey;
}
public String getNameKey() {
return nameKey;
}
public void setNameKey(String nameKey) {
this.nameKey = nameKey;
}
public String getWeightKey() {
return weightKey;
}
public void setWeightKey(String weightKey) {
this.weightKey = weightKey;
}
public String getChildrenKey() {
return childrenKey;
}
public void setChildrenKey(String childrenKey) {
this.childrenKey = childrenKey;
}
public Integer getDeep() {
return deep;
}
public void setDeep(Integer deep) {
this.deep = deep;
}
public Long getRootId() {
return rootId;
}
public void setRootId(Long rootId) {
this.rootId = rootId;
}
/**
* 生成 {@link TreeNodeConfig} 对象
*
* @return {@link TreeNodeConfig} 对象
*/
public TreeNodeConfig genTreeNodeConfig() {
return TreeNodeConfig.DEFAULT_CONFIG.setIdKey(idKey)
.setParentIdKey(parentIdKey)
.setNameKey(nameKey)
.setWeightKey(weightKey)
.setChildrenKey(childrenKey)
.setDeep(deep < 0 ? null : deep);
}
/**
* 根据 @TreeField 配置生成树结构配置
*
* @param treeField 树结构字段注解
* @return 树结构配置
*/
public TreeNodeConfig genTreeNodeConfig(TreeField treeField) {
CheckUtils.throwIfNull(treeField, "请添加并配置 @TreeField 树结构信息");
return new TreeNodeConfig().setIdKey(treeField.value())
.setParentIdKey(treeField.parentIdKey())
.setNameKey(treeField.nameKey())
.setWeightKey(treeField.weightKey())
.setChildrenKey(treeField.childrenKey())
.setDeep(treeField.deep() < 0 ? null : treeField.deep());
}
}

View File

@@ -28,34 +28,42 @@ public enum Api {
* 所有 API
*/
ALL,
/**
* 分页
*/
PAGE,
/**
* 树列表
*/
TREE,
/**
* 列表
*/
LIST,
/**
* 树列表
*/
TREE,
/**
* 详情
*/
GET,
/**
* 新增
*/
ADD,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 导出
*/

View File

@@ -54,11 +54,11 @@ public class LabelValueResp<T> implements Serializable {
private Boolean disabled;
/**
* 扩展
* 额外数据
*/
@Schema(description = "扩展")
@Schema(description = "额外数据")
@JsonInclude(JsonInclude.Include.NON_NULL)
private Object extend;
private Object extra;
public LabelValueResp() {
}
@@ -68,10 +68,10 @@ public class LabelValueResp<T> implements Serializable {
this.value = value;
}
public LabelValueResp(String label, T value, Object extend) {
public LabelValueResp(String label, T value, Object extra) {
this.label = label;
this.value = value;
this.extend = extend;
this.extra = extra;
}
public LabelValueResp(String label, T value, Boolean disabled) {
@@ -104,11 +104,11 @@ public class LabelValueResp<T> implements Serializable {
this.disabled = disabled;
}
public Object getExtend() {
return extend;
public Object getExtra() {
return extra;
}
public void setExtend(Object extend) {
this.extend = extend;
public void setExtra(Object extra) {
this.extra = extra;
}
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.crud.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.parser.NodeParser;
import cn.hutool.core.util.ReflectUtil;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.annotation.TreeField;
import java.util.ArrayList;
import java.util.List;
/**
* 树工具类
*
* @author Charles7c
* @since 1.0.0
*/
public class TreeUtils {
/**
* 默认字段配置对象(根据前端树结构灵活调整名称)
*/
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("title")
.setIdKey("key")
.setWeightKey("sort");
private TreeUtils() {
}
/**
* 树构建
*
* @param <T> 转换的实体 为数据源里的对象类型
* @param <E> ID类型
* @param list 源数据集合
* @param nodeParser 转换器
* @return List 树列表
*/
public static <T, E> List<Tree<E>> build(List<T> list, NodeParser<T, E> nodeParser) {
return build(list, DEFAULT_CONFIG, nodeParser);
}
/**
* 树构建
*
* @param <T> 转换的实体 为数据源里的对象类型
* @param <E> ID类型
* @param list 源数据集合
* @param treeNodeConfig 配置
* @param nodeParser 转换器
* @return List 树列表
*/
public static <T, E> List<Tree<E>> build(List<T> list, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) {
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
E parentId = (E)ReflectUtil.getFieldValue(list.get(0), treeNodeConfig.getParentIdKey());
return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser);
}
/**
* 根据 @TreeField 配置生成树结构配置
*
* @param treeField 树结构字段注解
* @return 树结构配置
*/
public static TreeNodeConfig genTreeNodeConfig(TreeField treeField) {
CheckUtils.throwIfNull(treeField, "请添加并配置 @TreeField 树结构信息");
return new TreeNodeConfig().setIdKey(treeField.value())
.setParentIdKey(treeField.parentIdKey())
.setNameKey(treeField.nameKey())
.setWeightKey(treeField.weightKey())
.setChildrenKey(treeField.childrenKey())
.setDeep(treeField.deep() < 0 ? null : treeField.deep());
}
}

View File

@@ -71,21 +71,6 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
return baseService.page(query, pageQuery);
}
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树列表信息
*/
@Operation(summary = "查询树列表", description = "查询树列表")
@ResponseBody
@GetMapping("/tree")
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
this.checkPermission(Api.LIST);
return baseService.tree(query, sortQuery, false);
}
/**
* 查询列表
*
@@ -101,6 +86,21 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
return baseService.list(query, sortQuery);
}
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树列表信息
*/
@Operation(summary = "查询树列表", description = "查询树列表")
@ResponseBody
@GetMapping("/tree")
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
this.checkPermission(Api.LIST);
return baseService.tree(query, sortQuery, false);
}
/**
* 查询详情
*

View File

@@ -46,16 +46,6 @@ public interface BaseService<L, D, Q, C> {
*/
PageResp<L> page(Q query, PageQuery pageQuery);
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段)
* @return 树列表信息
*/
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
/**
* 查询列表
*
@@ -65,6 +55,20 @@ public interface BaseService<L, D, Q, C> {
*/
List<L> list(Q query, SortQuery sortQuery);
/**
* 查询树列表
* <p>
* 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。
* 建议在前端进行查询过滤,如需使用建议重写方法。
* </p>
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
* @return 树列表信息
*/
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
/**
* 查看详情
*

View File

@@ -22,12 +22,14 @@ import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
import top.continew.starter.core.constant.StringConstants;
@@ -37,12 +39,13 @@ import top.continew.starter.data.mf.base.BaseMapper;
import top.continew.starter.data.mf.service.impl.ServiceImpl;
import top.continew.starter.data.mf.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.extension.crud.util.TreeUtils;
import top.continew.starter.file.excel.util.ExcelUtils;
import java.lang.reflect.Field;
@@ -79,26 +82,39 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
return pageResp;
}
@Override
public List<L> list(Q query, SortQuery sortQuery) {
List<L> list = this.list(query, sortQuery, listClass);
list.forEach(this::fill);
return list;
}
@Override
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
List<L> list = this.list(query, sortQuery);
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
// 如果构建简单树结构,则不包含基本树结构之外的扩展字段
TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG;
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
CrudTreeProperties treeProperties = crudProperties.getTree();
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
if (!isSimple) {
// 根据 @TreeField 配置生成树结构配置
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
TreeNodeConfig treeNodeConfig;
Long rootId;
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
if (isSimple) {
treeNodeConfig = treeProperties.genTreeNodeConfig();
rootId = treeProperties.getRootId();
} else {
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
rootId = treeField.rootId();
}
// 构建树
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
// 转换器
return TreeUtil.build(list, rootId, treeNodeConfig, (node, tree) -> {
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
// 如果构建简单树结构,则不包含扩展字段
if (!isSimple) {
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
@@ -109,13 +125,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
});
}
@Override
public List<L> list(Q query, SortQuery sortQuery) {
List<L> list = this.list(query, sortQuery, listClass);
list.forEach(this::fill);
return list;
}
@Override
public D get(Long id) {
T entity = super.getById(id);

View File

@@ -71,21 +71,6 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
return baseService.page(query, pageQuery);
}
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树列表信息
*/
@Operation(summary = "查询树列表", description = "查询树列表")
@ResponseBody
@GetMapping("/tree")
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
this.checkPermission(Api.LIST);
return baseService.tree(query, sortQuery, false);
}
/**
* 查询列表
*
@@ -101,6 +86,21 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
return baseService.list(query, sortQuery);
}
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树列表信息
*/
@Operation(summary = "查询树列表", description = "查询树列表")
@ResponseBody
@GetMapping("/tree")
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
this.checkPermission(Api.LIST);
return baseService.tree(query, sortQuery, false);
}
/**
* 查询详情
*

View File

@@ -46,16 +46,6 @@ public interface BaseService<L, D, Q, C> {
*/
PageResp<L> page(Q query, PageQuery pageQuery);
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段)
* @return 树列表信息
*/
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
/**
* 查询列表
*
@@ -65,6 +55,20 @@ public interface BaseService<L, D, Q, C> {
*/
List<L> list(Q query, SortQuery sortQuery);
/**
* 查询树列表
* <p>
* 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。
* 建议在前端进行查询过滤,如需使用建议重写方法。
* </p>
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
* @return 树列表信息
*/
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
/**
* 查询详情
*

View File

@@ -20,8 +20,10 @@ import cn.crane4j.core.support.OperateTemplate;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
@@ -42,20 +44,18 @@ import top.continew.starter.data.mp.service.impl.ServiceImpl;
import top.continew.starter.data.mp.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.annotation.DictField;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.extension.crud.util.TreeUtils;
import top.continew.starter.file.excel.util.ExcelUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
/**
* 业务实现基类
@@ -86,28 +86,41 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
return pageResp;
}
@Override
public List<L> list(Q query, SortQuery sortQuery) {
List<L> list = this.list(query, sortQuery, this.getListClass());
list.forEach(this::fill);
return list;
}
@Override
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
List<L> list = this.list(query, sortQuery);
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
// 如果构建简单树结构,则不包含基本树结构之外的扩展字段
TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG;
TreeField treeField = this.getListClass().getDeclaredAnnotation(TreeField.class);
if (!isSimple) {
// 根据 @TreeField 配置生成树结构配置
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
CrudTreeProperties treeProperties = crudProperties.getTree();
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
TreeNodeConfig treeNodeConfig;
Long rootId;
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
if (isSimple) {
treeNodeConfig = treeProperties.genTreeNodeConfig();
rootId = treeProperties.getRootId();
} else {
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
rootId = treeField.rootId();
}
// 构建树
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
// 转换器
return TreeUtil.build(list, rootId, treeNodeConfig, (node, tree) -> {
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
// 如果构建简单树结构,则不包含扩展字段
if (!isSimple) {
List<Field> fieldList = ReflectUtils.getNonStaticFields(this.getListClass());
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
@@ -116,13 +129,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
});
}
@Override
public List<L> list(Q query, SortQuery sortQuery) {
List<L> list = this.list(query, sortQuery, this.getListClass());
list.forEach(this::fill);
return list;
}
@Override
public D get(Long id) {
T entity = super.getById(id, false);
@@ -133,19 +139,40 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
@Override
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
this.sort(queryWrapper, sortQuery);
DictField dictField = super.getEntityClass().getDeclaredAnnotation(DictField.class);
CheckUtils.throwIfNull(dictField, "请添加并配置 @DictField 字典结构信息");
// 指定查询字典字段
queryWrapper.select(dictField.labelKey(), dictField.valueKey());
List<T> entityList = baseMapper.selectList(queryWrapper);
List<L> list = this.list(query, sortQuery);
// 解析映射
Map<String, String> fieldMapping = MapUtil.newHashMap(2);
fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.labelKey()), "label");
fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.valueKey()), "value");
return BeanUtil.copyToList(entityList, LabelValueResp.class, CopyOptions.create()
.setFieldMapping(fieldMapping));
List<LabelValueResp> respList = new ArrayList<>(list.size());
String labelKey = dictField.labelKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictField.labelKey(), StringConstants.DOT, true)
: dictField.labelKey();
String valueKey = dictField.valueKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictField.valueKey(), StringConstants.DOT, true)
: dictField.valueKey();
List<String> extraFieldNames = Arrays.stream(dictField.extraKeys())
.map(extraKey -> extraKey.contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(extraKey, StringConstants.DOT, true)
: extraKey)
.map(CharSequenceUtil::toCamelCase)
.toList();
for (L entity : list) {
LabelValueResp<Object> labelValueResp = new LabelValueResp<>();
labelValueResp.setLabel(Convert.toStr(ReflectUtil.getFieldValue(entity, CharSequenceUtil
.toCamelCase(labelKey))));
labelValueResp.setValue(ReflectUtil.getFieldValue(entity, CharSequenceUtil.toCamelCase(valueKey)));
respList.add(labelValueResp);
if (CollUtil.isEmpty(extraFieldNames)) {
continue;
}
// 额外数据
Map<String, Object> extraMap = MapUtil.newHashMap(dictField.extraKeys().length);
for (String extraFieldName : extraFieldNames) {
extraMap.put(extraFieldName, ReflectUtil.getFieldValue(entity, extraFieldName));
}
labelValueResp.setExtra(extraMap);
}
return respList;
}
@Override

View File

@@ -22,12 +22,12 @@ import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
@@ -110,7 +110,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
}
}
return null != where ? new AndExpression(where, new Parenthesis(expression)) : expression;
return null != where ? new AndExpression(where, new ParenthesedExpressionList<>(expression)) : expression;
}
/**
@@ -138,7 +138,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
Function function = new Function();
function.setName("find_in_set");
function.setParameters(new ExpressionList(new LongValue(userContext.getDeptId()), new Column("ancestors")));
function.setParameters(new ExpressionList<>(new LongValue(userContext.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function));
subSelect.setSelect(select);
// 构建父查询
@@ -199,7 +199,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
* </p>
*
* @param dataPermission 数据权限
* @param roleContext 角色上下文
* @param roleContext 角色上下文
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/

View File

@@ -1,9 +1,24 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.autoconfigure;
import cn.hutool.core.convert.Convert;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import top.continew.starter.extension.tenant.context.TenantContext;
import top.continew.starter.extension.tenant.context.TenantContextHolder;

View File

@@ -16,12 +16,9 @@
package top.continew.starter.extension.tenant.autoconfigure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.core.constant.PropertiesConstants;

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.config;
/**

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.config;
/**

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.context;
/**

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.context;
import com.alibaba.ttl.TransmittableThreadLocal;

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.enums;
/**

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.handler;
import top.continew.starter.extension.tenant.config.TenantDataSource;

View File

@@ -111,7 +111,9 @@ public class TenantAutoConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TenantDataSourceHandler tenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider, DynamicRoutingDataSource dynamicRoutingDataSource, DefaultDataSourceCreator dataSourceCreator) {
public TenantDataSourceHandler tenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
DynamicRoutingDataSource dynamicRoutingDataSource,
DefaultDataSourceCreator dataSourceCreator) {
return new DefaultTenantDataSourceHandler(tenantDataSourceProvider, dynamicRoutingDataSource, dataSourceCreator);
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.handler;
import cn.hutool.core.text.CharSequenceUtil;
@@ -25,7 +41,9 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
private final DefaultDataSourceCreator dataSourceCreator;
private final TenantDataSourceProvider tenantDataSourceProvider;
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider, DynamicRoutingDataSource dynamicRoutingDataSource, DefaultDataSourceCreator dataSourceCreator) {
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
DynamicRoutingDataSource dynamicRoutingDataSource,
DefaultDataSourceCreator dataSourceCreator) {
this.tenantDataSourceProvider = tenantDataSourceProvider;
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
this.dataSourceCreator = dataSourceCreator;
@@ -36,7 +54,8 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
if (!this.containsDataSource(dataSourceName)) {
TenantDataSource tenantDataSource = tenantDataSourceProvider.getByTenantId(dataSourceName);
if (null == tenantDataSource) {
throw new IllegalArgumentException("Data source [%s] configuration not found".formatted(dataSourceName));
throw new IllegalArgumentException("Data source [%s] configuration not found"
.formatted(dataSourceName));
}
DataSource datasource = this.createDataSource(tenantDataSource);
dynamicRoutingDataSource.addDataSource(dataSourceName, datasource);

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.handler;
import cn.hutool.core.collection.CollUtil;

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.handler;
import org.aopalliance.aop.Advice;
@@ -50,6 +66,6 @@ public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements
private Pointcut buildPointcut() {
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
cut.setExpression("!@annotation(top.continew.starter.extension.tenant.annotation.TenantDataSourceIgnore)");
return new ComposablePointcut((Pointcut) cut);
return new ComposablePointcut((Pointcut)cut);
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <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 top.continew.starter.extension.tenant.handler;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;

View File

@@ -15,6 +15,8 @@
<modules>
<module>continew-starter-extension-crud</module>
<module>continew-starter-extension-datapermission</module>
<module>continew-starter-extension-tenant</module>
</modules>
<dependencies>