diff --git a/README.md b/README.md index a7b6e2fa..63182fb1 100644 --- a/README.md +++ b/README.md @@ -236,10 +236,10 @@ ContiNew Starter 的分支目前分为下个大版本的开发分支和上个大 ### 特别鸣谢 -- 感谢 JetBrains 提供的 非商业开源软件开发授权 +- 感谢 JetBrains 提供的 非商业开源软件开发授权 - 感谢 MyBatis PlusSa-TokenJetCacheCrane4jKnife4jHutool 等开源组件作者为国内开源世界作出的贡献 - 感谢 ELADMINRuoYi-Vue-PlusDante-Engine,致敬各位作者为开源脚手架领域作出的贡献 - - e.g. 扩展于 ELADMIN 项目开源的 QueryHelper 组件 + - e.g. 起源于 ELADMIN 项目开源的 QueryHelper 组件 - e.g. 扩展于 RuoYi-Vue-Plus 项目封装的 SaToken 相关认证鉴权配置 - e.g. 扩展于 Dante-Engine 项目封装的 Redisson 相关配置 - 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire: diff --git a/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/Query.java b/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/Query.java index 7b6ec2f2..96315569 100644 --- a/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/Query.java +++ b/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/Query.java @@ -23,7 +23,6 @@ import java.lang.annotation.*; * * @author Charles7c * @author Jasmine - * @author Zheng Jie(ELADMIN) * @since 1.0.0 */ @Target(ElementType.FIELD) @@ -32,7 +31,7 @@ import java.lang.annotation.*; public @interface Query { /** - * 列名 + * 列名(注意:列名是数据库字段名,而不是实体类字段名。如果命名是数据库关键字的,请使用转义符包裹) * *

* columns 为空时,默认取值字段名(自动转换为下划线命名);
diff --git a/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/QueryHelper.java b/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/QueryHelper.java deleted file mode 100644 index f87f667a..00000000 --- a/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/QueryHelper.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - *

- * 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 - *

- * http://www.gnu.org/licenses/lgpl.html - *

- * 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.charles7c.continew.starter.data.mybatis.plus.query; - -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import top.charles7c.continew.starter.core.exception.BadRequestException; -import top.charles7c.continew.starter.core.util.ReflectUtils; -import top.charles7c.continew.starter.core.util.validate.ValidationUtils; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -/** - * 查询助手 - * - * @author Charles7c - * @author Jasmine - * @author Zheng Jie(ELADMIN) - * @since 1.0.0 - */ -@Slf4j -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class QueryHelper { - - /** - * 根据查询条件构建查询条件封装对象 - * - * @param query 查询条件 - * @param 查询条件数据类型 - * @param 查询数据类型 - * @return 查询条件封装对象 - */ - public static QueryWrapper build(Q query) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - // 没有查询条件,直接返回 - if (null == query) { - return queryWrapper; - } - // 解析并拼接查询条件 - List fieldList = ReflectUtils.getNonStaticFields(query.getClass()); - fieldList.forEach(field -> buildWrapper(query, field, queryWrapper)); - return queryWrapper; - } - - /** - * 构建查询条件封装对象 - * - * @param query 查询条件 - * @param field 字段 - * @param queryWrapper 查询条件封装对象 - * @param 查询条件数据类型 - * @param 查询数据类型 - */ - public static void buildWrapper(Q query, Field field, QueryWrapper queryWrapper) { - boolean accessible = field.canAccess(query); - try { - field.setAccessible(true); - // 如果字段值为空,直接返回 - Object fieldValue = field.get(query); - if (ObjectUtil.isEmpty(fieldValue)) { - return; - } - // 建议:数据库规范中列建议采用下划线连接法命名,程序规范中变量建议采用驼峰法命名 - String fieldName = field.getName(); - String columnName = StrUtil.toUnderlineCase(fieldName); - // 没有 @Query 注解,默认等值查询 - Query queryAnnotation = field.getAnnotation(Query.class); - if (null == queryAnnotation) { - queryWrapper.eq(columnName, fieldValue); - return; - } - // 解析单列查询 - String[] columns = queryAnnotation.columns(); - final int columnLength = ArrayUtil.length(columns); - if (columnLength == 0 || columnLength == 1) { - columnName = columnLength == 1 ? columns[0] : columnName; - parse(queryAnnotation.type(), columnName, fieldValue, queryWrapper); - return; - } - // 解析多列查询 - QueryType queryType = queryAnnotation.type(); - queryWrapper.nested(wrapper -> { - for (String column : columns) { - switch (queryType) { - case EQ -> queryWrapper.or().eq(column, fieldValue); - case NE -> queryWrapper.or().ne(column, fieldValue); - case GT -> queryWrapper.or().gt(column, fieldValue); - case GE -> queryWrapper.or().ge(column, fieldValue); - case LT -> queryWrapper.or().lt(column, fieldValue); - case LE -> queryWrapper.or().le(column, fieldValue); - case BETWEEN -> { - List between = new ArrayList<>((List)fieldValue); - ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", fieldName); - queryWrapper.or().between(column, between.get(0), between.get(1)); - } - case LIKE -> queryWrapper.or().like(column, fieldValue); - case LIKE_LEFT -> queryWrapper.or().likeLeft(column, fieldValue); - case LIKE_RIGHT -> queryWrapper.or().likeRight(column, fieldValue); - case IN -> { - ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName); - queryWrapper.or().in(column, (List)fieldValue); - } - case NOT_IN -> { - ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName); - queryWrapper.or().notIn(column, (List)fieldValue); - } - case IS_NULL -> queryWrapper.or().isNull(column); - case IS_NOT_NULL -> queryWrapper.or().isNotNull(column); - default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType)); - } - } - }); - } catch (BadRequestException e) { - log.error("Build query occurred an validation error: {}. Query: {}, Field: {}.", e - .getMessage(), query, field, e); - throw e; - } catch (Exception e) { - log.error("Build query occurred an error: {}. Query: {}, Field: {}.", e.getMessage(), query, field, e); - } finally { - field.setAccessible(accessible); - } - } - - /** - * 解析查询条件 - * - * @param queryType 查询类型 - * @param columnName 驼峰字段名 - * @param fieldValue 字段值 - * @param queryWrapper 查询条件封装对象 - * @param 查询数据类型 - */ - private static void parse(QueryType queryType, - String columnName, - Object fieldValue, - QueryWrapper queryWrapper) { - switch (queryType) { - case EQ -> queryWrapper.eq(columnName, fieldValue); - case NE -> queryWrapper.ne(columnName, fieldValue); - case GT -> queryWrapper.gt(columnName, fieldValue); - case GE -> queryWrapper.ge(columnName, fieldValue); - case LT -> queryWrapper.lt(columnName, fieldValue); - case LE -> queryWrapper.le(columnName, fieldValue); - case BETWEEN -> { - List between = new ArrayList<>((List)fieldValue); - ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName); - queryWrapper.between(columnName, between.get(0), between.get(1)); - } - case LIKE -> queryWrapper.like(columnName, fieldValue); - case LIKE_LEFT -> queryWrapper.likeLeft(columnName, fieldValue); - case LIKE_RIGHT -> queryWrapper.likeRight(columnName, fieldValue); - case IN -> { - ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName); - queryWrapper.in(columnName, (List)fieldValue); - } - case NOT_IN -> { - ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName); - queryWrapper.notIn(columnName, (List)fieldValue); - } - case IS_NULL -> queryWrapper.isNull(columnName); - case IS_NOT_NULL -> queryWrapper.isNotNull(columnName); - default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType)); - } - } -} diff --git a/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/QueryWrapperHelper.java b/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/QueryWrapperHelper.java new file mode 100644 index 00000000..9ec6792a --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-plus/src/main/java/top/charles7c/continew/starter/data/mybatis/plus/query/QueryWrapperHelper.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.charles7c.continew.starter.data.mybatis.plus.query; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import top.charles7c.continew.starter.core.exception.BadRequestException; +import top.charles7c.continew.starter.core.util.ReflectUtils; +import top.charles7c.continew.starter.core.util.validate.ValidationUtils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * QueryWrapper 助手 + * + * @author Charles7c + * @author Jasmine + * @since 1.0.0 + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class QueryWrapperHelper { + + /** + * 构建 QueryWrapper + * + * @param query 查询条件 + * @param 查询条件数据类型 + * @param 查询数据类型 + * @return QueryWrapper + */ + public static QueryWrapper build(Q query) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 没有查询条件,直接返回 + if (null == query) { + return queryWrapper; + } + // 获取查询条件中所有的字段 + List fieldList = ReflectUtils.getNonStaticFields(query.getClass()); + return build(query, fieldList, queryWrapper); + } + + /** + * 构建 QueryWrapper + * + * @param query 查询条件 + * @param fields 查询条件字段列表 + * @param queryWrapper QueryWrapper + * @param 查询条件数据类型 + * @param 查询数据类型 + * @return QueryWrapper + */ + public static QueryWrapper build(Q query, List fields, QueryWrapper queryWrapper) { + // 没有查询条件,直接返回 + if (null == query) { + return queryWrapper; + } + // 解析并拼接查询条件 + for (Field field : fields) { + List>> consumers = buildWrapperConsumer(query, field); + queryWrapper.and(CollUtil.isNotEmpty(consumers), q -> consumers.forEach(q::or)); + } + return queryWrapper; + } + + /** + * 构建 QueryWrapper Consumer + * + * @param query 查询条件 + * @param field 查询条件字段 + * @param 查询条件数据类型 + * @param 查询数据类型 + * @return QueryWrapper Consumer + */ + public static List>> buildWrapperConsumer(Q query, Field field) { + List>> consumers = new ArrayList<>(); + boolean accessible = field.canAccess(query); + try { + field.setAccessible(true); + // 如果字段值为空,直接返回 + Object fieldValue = field.get(query); + if (ObjectUtil.isEmpty(fieldValue)) { + return consumers; + } + // 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名 + String fieldName = field.getName(); + // 没有 @Query 注解,默认等值查询 + Query queryAnnotation = field.getAnnotation(Query.class); + if (Objects.isNull(queryAnnotation)) { + consumers.add(q -> q.eq(StrUtil.toUnderlineCase(fieldName), fieldValue)); + return consumers; + } + // 解析单列查询 + QueryType queryType = queryAnnotation.type(); + String[] columns = queryAnnotation.columns(); + final int columnLength = ArrayUtil.length(columns); + if (columnLength <= 1) { + String columnName = columnLength == 1 ? columns[0] : StrUtil.toUnderlineCase(fieldName); + parse(queryType, columnName, fieldValue, consumers); + return consumers; + } + // 解析多列查询 + for (String column : columns) { + parse(queryType, column, fieldValue, consumers); + } + } catch (BadRequestException e) { + log.error("Build query wrapper occurred an validation error: {}. Query: {}, Field: {}.", e + .getMessage(), query, field, e); + throw e; + } catch (Exception e) { + log.error("Build query wrapper occurred an error: {}. Query: {}, Field: {}.", e + .getMessage(), query, field, e); + } finally { + field.setAccessible(accessible); + } + return consumers; + } + + /** + * 解析查询条件 + * + * @param queryType 查询类型 + * @param columnName 列名 + * @param fieldValue 字段值 + * @param 查询数据类型 + */ + private static void parse(QueryType queryType, + String columnName, + Object fieldValue, + List>> consumers) { + switch (queryType) { + case EQ -> consumers.add(q -> q.eq(columnName, fieldValue)); + case NE -> consumers.add(q -> q.ne(columnName, fieldValue)); + case GT -> consumers.add(q -> q.gt(columnName, fieldValue)); + case GE -> consumers.add(q -> q.ge(columnName, fieldValue)); + case LT -> consumers.add(q -> q.lt(columnName, fieldValue)); + case LE -> consumers.add(q -> q.le(columnName, fieldValue)); + case BETWEEN -> { + List between = new ArrayList<>((List)fieldValue); + ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName); + consumers.add(q -> q.between(columnName, between.get(0), between.get(1))); + } + case LIKE -> consumers.add(q -> q.like(columnName, fieldValue)); + case LIKE_LEFT -> consumers.add(q -> q.likeLeft(columnName, fieldValue)); + case LIKE_RIGHT -> consumers.add(q -> q.likeRight(columnName, fieldValue)); + case IN -> { + ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName); + consumers.add(q -> q.in(columnName, (List)fieldValue)); + } + case NOT_IN -> { + ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName); + consumers.add(q -> q.notIn(columnName, (List)fieldValue)); + } + case IS_NULL -> consumers.add(q -> q.isNull(columnName)); + case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName)); + default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType)); + } + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java index f9b43be6..626a21de 100644 --- a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java @@ -40,7 +40,7 @@ import top.charles7c.continew.starter.core.util.ReflectUtils; import top.charles7c.continew.starter.core.util.validate.CheckUtils; import top.charles7c.continew.starter.core.util.validate.ValidationUtils; import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper; -import top.charles7c.continew.starter.data.mybatis.plus.query.QueryHelper; +import top.charles7c.continew.starter.data.mybatis.plus.query.QueryWrapperHelper; import top.charles7c.continew.starter.extension.crud.annotation.TreeField; import top.charles7c.continew.starter.extension.crud.model.query.PageQuery; import top.charles7c.continew.starter.extension.crud.model.query.SortQuery; @@ -81,7 +81,7 @@ public abstract class BaseServiceImpl, T extends BaseDO, @Override public PageResp page(Q query, PageQuery pageQuery) { - QueryWrapper queryWrapper = handleQueryWrapper(query); + QueryWrapper queryWrapper = this.handleQueryWrapper(query); IPage page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper); PageResp pageResp = PageResp.build(page, listClass); pageResp.getList().forEach(this::fill); @@ -134,7 +134,7 @@ public abstract class BaseServiceImpl, T extends BaseDO, * @return 列表信息 */ protected List list(Q query, SortQuery sortQuery, Class targetClass) { - QueryWrapper queryWrapper = handleQueryWrapper(query); + QueryWrapper queryWrapper = this.handleQueryWrapper(query); // 设置排序 this.sort(queryWrapper, sortQuery); List entityList = baseMapper.selectList(queryWrapper); @@ -248,19 +248,14 @@ public abstract class BaseServiceImpl, T extends BaseDO, } /** - * 封装查询条件 + * 处理查询条件 * - * @return 查询条件封装对象 + * @return QueryWrapper */ protected QueryWrapper handleQueryWrapper(Q query) { QueryWrapper queryWrapper = new QueryWrapper<>(); - // 没有查询条件,直接返回 - if (null == query) { - return queryWrapper; - } // 解析并拼接查询条件 - queryFields.forEach(field -> QueryHelper.buildWrapper(query, field, queryWrapper)); - return queryWrapper; + return QueryWrapperHelper.build(query, queryFields, queryWrapper); } /**