diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/pom.xml b/continew-starter-data/continew-starter-data-mybatis-flex/pom.xml new file mode 100644 index 00000000..47dfe25d --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + top.continew + continew-starter-data + ${revision} + + + continew-starter-data-mybatis-flex + ContiNew Starter 数据访问模块 - MyBatis Flex + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + com.mybatis-flex + mybatis-flex-processor + provided + + + + + top.continew + continew-starter-data-core + + + + + me.ahoo.cosid + cosid-spring-boot-starter + true + + + \ No newline at end of file diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/ConditionalOnEnabledDataPermission.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/ConditionalOnEnabledDataPermission.java new file mode 100644 index 00000000..f868d466 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/ConditionalOnEnabledDataPermission.java @@ -0,0 +1,35 @@ +/* + * 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.continew.starter.data.mybatis.flex.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import top.continew.starter.core.constant.PropertiesConstants; + +import java.lang.annotation.*; + +/** + * 是否启用数据权限注解 + * + * @author hellokaton + * @since 2.0.2 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@ConditionalOnProperty(prefix = "mybatis-flex.extension.data-permission", name = PropertiesConstants.ENABLED, havingValue = "true") +public @interface ConditionalOnEnabledDataPermission { +} \ No newline at end of file diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/MyBatisFlexExtensionProperties.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/MyBatisFlexExtensionProperties.java new file mode 100644 index 00000000..bf088dc6 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/MyBatisFlexExtensionProperties.java @@ -0,0 +1,162 @@ +/* + * 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.continew.starter.data.mybatis.flex.autoconfigure; + +import com.mybatisflex.core.dialect.DbType; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * MyBatis Plus 扩展配置属性 + * + * @author Charles7c + * @since 1.0.0 + */ +@ConfigurationProperties(prefix = "mybatis-flex.extension") +public class MyBatisFlexExtensionProperties { + + /** + * 是否启用扩展 + */ + private boolean enabled = false; + + /** + * Mapper 接口扫描包(配置时必须使用:mapper-package 键名) + *

+ * e.g. com.example.**.mapper + *

+ */ + private String mapperPackage; + + /** + * 数据权限插件配置 + */ + private DataPermissionProperties dataPermission; + + /** + * 分页插件配置 + */ + private PaginationProperties pagination; + + /** + * 数据权限插件配置属性 + */ + public static class DataPermissionProperties { + + /** + * 是否启用数据权限插件 + */ + private boolean enabled = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + /** + * 分页插件配置属性 + */ + public static class PaginationProperties { + + /** + * 是否启用分页插件 + */ + private boolean enabled = true; + + /** + * 数据库类型 + */ + private DbType dbType; + + /** + * 是否溢出处理 + */ + private boolean overflow = false; + + /** + * 单页分页条数限制(默认:-1 表示无限制) + */ + private Long maxLimit = -1L; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public DbType getDbType() { + return dbType; + } + + public void setDbType(DbType dbType) { + this.dbType = dbType; + } + + public boolean isOverflow() { + return overflow; + } + + public void setOverflow(boolean overflow) { + this.overflow = overflow; + } + + public Long getMaxLimit() { + return maxLimit; + } + + public void setMaxLimit(Long maxLimit) { + this.maxLimit = maxLimit; + } + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getMapperPackage() { + return mapperPackage; + } + + public void setMapperPackage(String mapperPackage) { + this.mapperPackage = mapperPackage; + } + + public DataPermissionProperties getDataPermission() { + return dataPermission; + } + + public void setDataPermission(DataPermissionProperties dataPermission) { + this.dataPermission = dataPermission; + } + + public PaginationProperties getPagination() { + return pagination; + } + + public void setPagination(PaginationProperties pagination) { + this.pagination = pagination; + } +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/MybatisFlexAutoConfiguration.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/MybatisFlexAutoConfiguration.java new file mode 100644 index 00000000..407c1792 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/autoconfigure/MybatisFlexAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * 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.continew.starter.data.mybatis.flex.autoconfigure; + +import com.mybatisflex.core.dialect.DbType; +import com.mybatisflex.core.dialect.DialectFactory; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import org.mybatis.spring.annotation.MapperScan; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import top.continew.starter.core.constant.PropertiesConstants; +import top.continew.starter.core.util.GeneralPropertySourceFactory; +import top.continew.starter.data.mybatis.flex.datapermission.DataPermissionDialect; +import top.continew.starter.data.mybatis.flex.datapermission.DataPermissionFilter; + +/** + * MyBatis Flex 自动配置 + * + * @author hellokaton + * @since 2.0.2 + */ +@AutoConfiguration +@MapperScan("${mybatis-flex.extension.mapper-package}") +@EnableTransactionManagement(proxyTargetClass = true) +@EnableConfigurationProperties(MyBatisFlexExtensionProperties.class) +@ConditionalOnProperty(prefix = "mybatis-flex.extension", name = PropertiesConstants.ENABLED, havingValue = "true") +@PropertySource(value = "classpath:default-data-mybatis-flex.yml", factory = GeneralPropertySourceFactory.class) +public class MybatisFlexAutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(MybatisFlexAutoConfiguration.class); + + @Resource + private DataPermissionFilter dataPermissionFilter; + + @PostConstruct + public void postConstruct() { + log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Flex' completed initialization."); + DialectFactory.registerDialect(DbType.MYSQL, new DataPermissionDialect(dataPermissionFilter)); + } + +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/base/BaseMapper.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/base/BaseMapper.java new file mode 100644 index 00000000..57217a4b --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/base/BaseMapper.java @@ -0,0 +1,71 @@ +/* + * 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.continew.starter.data.mybatis.flex.base; + +import cn.hutool.core.util.ClassUtil; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.row.Db; + +import java.util.Collection; + +/** + * Mapper 基类 + * + * @param 实体类 + * @author hellokaton + * @since 1.0.0 + */ +public interface BaseMapper extends com.mybatisflex.core.BaseMapper { + + /** + * 批量更新记录 + * + * @param entityList 实体列表 + * @return 是否成功 + */ + default boolean updateBatchById(Collection entityList) { + return Db.updateEntitiesBatch(entityList) > 0; + } + + /** + * 链式查询 + * + * @return QueryWrapper 的包装类 + */ + default QueryWrapper query() { + return QueryWrapper.create(); + } + + /** + * 链式查询 + * + * @return QueryWrapper 的包装类 + */ + default QueryWrapper query(T entity) { + return QueryWrapper.create(entity); + } + + /** + * 获取实体类 Class 对象 + * + * @return 实体类 Class 对象 + */ + default Class currentEntityClass() { + return (Class)ClassUtil.getTypeArgument(this.getClass(), 0); + } + +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/base/IBaseEnum.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/base/IBaseEnum.java new file mode 100644 index 00000000..748a7537 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/base/IBaseEnum.java @@ -0,0 +1,50 @@ +/* + * 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.continew.starter.data.mybatis.flex.base; + +import java.io.Serializable; + +/** + * 枚举接口 + * + * @param value 类型 + * @author hellokaton + * @since 1.0.0 + */ +public interface IBaseEnum { + + /** + * 枚举描述 + * + * @return 枚举描述 + */ + String getDescription(); + + /** + * 枚举数据库存储值 + */ + T getValue(); + + /** + * 颜色 + * + * @return 颜色 + */ + default String getColor() { + return null; + } +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermission.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermission.java new file mode 100644 index 00000000..d2c9718d --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermission.java @@ -0,0 +1,75 @@ +/* + * 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.continew.starter.data.mybatis.flex.datapermission; + +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.*; + +/** + * 数据权限注解 + * + * @author hellokaton + * @since 2.0.2 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + + /** + * Alias for the {@link #tableAlias()} attribute. + */ + @AliasFor("tableAlias") + String value() default ""; + + /** + * 表别名 + */ + @AliasFor("value") + String tableAlias() default ""; + + /** + * ID + */ + String id() default "id"; + + /** + * 部门 ID + */ + String deptId() default "dept_id"; + + /** + * 用户 ID + */ + String userId() default "create_user"; + + /** + * 角色 ID(角色和部门关联表) + */ + String roleId() default "role_id"; + + /** + * 部门表别名 + */ + String deptTableAlias() default "sys_dept"; + + /** + * 角色和部门关联表别名 + */ + String roleDeptTableAlias() default "sys_role_dept"; +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionAspect.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionAspect.java new file mode 100644 index 00000000..f74c0860 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionAspect.java @@ -0,0 +1,50 @@ +/* + * 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.continew.starter.data.mybatis.flex.datapermission; + +import org.aspectj.lang.annotation.*; + +@Aspect +public class DataPermissionAspect { + + // ThreadLocal用于存储注解信息 + private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + @Pointcut("@annotation(dataPermission)") + public void dataPermissionPointcut(DataPermission dataPermission) { + } + + @Before("dataPermissionPointcut(dataPermission)") + public void beforeMethod(DataPermission dataPermission) { + THREAD_LOCAL.set(dataPermission); + } + + @AfterThrowing(pointcut = "dataPermissionPointcut(dataPermission)") + public void afterThrowingMethod(DataPermission dataPermission) { + THREAD_LOCAL.remove(); + } + + @After("dataPermissionPointcut(dataPermission)") + public void afterMethod(DataPermission dataPermission) { + THREAD_LOCAL.remove(); + } + + public static DataPermission currentDataPermission() { + return THREAD_LOCAL.get(); + } + +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionCurrentUser.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionCurrentUser.java new file mode 100644 index 00000000..366b7b3e --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionCurrentUser.java @@ -0,0 +1,107 @@ +/* + * 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.continew.starter.data.mybatis.flex.datapermission; + +import java.util.Set; + +/** + * 当前用户信息 + * + * @author hellokaton + * @since 2.0.2 + */ +public class DataPermissionCurrentUser { + + /** + * 用户 ID + */ + private String userId; + + /** + * 角色列表 + */ + private Set roles; + + /** + * 部门 ID + */ + private String deptId; + + /** + * 当前用户角色信息 + */ + public static class CurrentUserRole { + + /** + * 角色 ID + */ + private String roleId; + + /** + * 数据权限 + */ + private DataScope dataScope; + + public CurrentUserRole() { + } + + public CurrentUserRole(String roleId, DataScope dataScope) { + this.roleId = roleId; + this.dataScope = dataScope; + } + + public String getRoleId() { + return roleId; + } + + public void setRoleId(String roleId) { + this.roleId = roleId; + } + + public DataScope getDataScope() { + return dataScope; + } + + public void setDataScope(DataScope dataScope) { + this.dataScope = dataScope; + } + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionDialect.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionDialect.java new file mode 100644 index 00000000..1c696b16 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionDialect.java @@ -0,0 +1,161 @@ +/* + * 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.continew.starter.data.mybatis.flex.datapermission; + +import cn.hutool.core.util.StrUtil; +import com.mybatisflex.core.dialect.impl.CommonsDialectImpl; +import com.mybatisflex.core.query.QueryWrapper; + +import java.util.Set; + +/** + * 数据权限处理器实现类 + * + * @author 数据权限 + * @author hellokaton + * @since 2.0.2 + */ +public class DataPermissionDialect extends CommonsDialectImpl { + + private final DataPermissionFilter dataPermissionFilter; + + public DataPermissionDialect(DataPermissionFilter dataPermissionFilter) { + this.dataPermissionFilter = dataPermissionFilter; + } + + @Override + public String forSelectByQuery(QueryWrapper queryWrapper) { + if (!dataPermissionFilter.isFilter()) { + return super.buildSelectSql(queryWrapper); + } + DataPermission dataPermission = DataPermissionAspect.currentDataPermission(); + if (null == dataPermission) { + return super.buildSelectSql(queryWrapper); + } + DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser(); + Set roles = currentUser.getRoles(); + for (DataPermissionCurrentUser.CurrentUserRole role : roles) { + DataScope dataScope = role.getDataScope(); + if (DataScope.ALL.equals(dataScope)) { + return super.buildSelectSql(queryWrapper); + } + switch (dataScope) { + case DEPT_AND_CHILD -> this.buildDeptAndChildExpression(dataPermission, currentUser, queryWrapper); + case DEPT -> this.buildDeptExpression(dataPermission, currentUser, queryWrapper); + case SELF -> this.buildSelfExpression(dataPermission, currentUser, queryWrapper); + case CUSTOM -> this.buildCustomExpression(dataPermission, role, queryWrapper); + default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope)); + } + } + return super.buildSelectSql(queryWrapper); + } + + /** + * 构建自定义数据权限表达式 + * + *

+ * 处理完后的 SQL 示例:
select t1.* from table as t1 where t1.dept_id in (select dept_id from sys_role_dept + * where role_id = xxx); + *

+ * + * @param dataPermission 数据权限 + * @param role 当前用户角色 + * @param queryWrapper 查询条件 + * @return 处理完后的表达式 + */ + private void buildCustomExpression(DataPermission dataPermission, + DataPermissionCurrentUser.CurrentUserRole role, + QueryWrapper queryWrapper) { + QueryWrapper subQueryWrapper = QueryWrapper.create(); + subQueryWrapper.select(dataPermission.deptId()).from(dataPermission.roleDeptTableAlias()); + subQueryWrapper.eq(dataPermission.roleId(), role.getRoleId()); + queryWrapper.in(buildColumn(dataPermission.tableAlias(), dataPermission.deptId()), subQueryWrapper); + } + + /** + * 构建仅本人数据权限表达式 + * + *

+ * 处理完后的 SQL 示例:
select t1.* from table as t1 where t1.create_user = xxx; + *

+ * + * @param dataPermission 数据权限 + * @param currentUser 当前用户 + * @param queryWrapper 处理前的表达式 + */ + private void buildSelfExpression(DataPermission dataPermission, + DataPermissionCurrentUser currentUser, + QueryWrapper queryWrapper) { + queryWrapper.eq(buildColumn(dataPermission.tableAlias(), dataPermission.userId()), currentUser.getUserId()); + } + + /** + * 构建本部门数据权限表达式 + * + *

+ * 处理完后的 SQL 示例:
select t1.* from table as t1 where t1.dept_id = xxx; + *

+ * + * @param dataPermission 数据权限 + * @param currentUser 当前用户 + * @param queryWrapper 查询条件 + */ + private void buildDeptExpression(DataPermission dataPermission, + DataPermissionCurrentUser currentUser, + QueryWrapper queryWrapper) { + queryWrapper.eq(buildColumn(dataPermission.tableAlias(), dataPermission.deptId()), currentUser.getDeptId()); + } + + /** + * 构建本部门及以下数据权限表达式 + * + *

+ * 处理完后的 SQL 示例:
select t1.* from table as t1 where t1.dept_id in (select id from sys_dept where id = + * xxx or find_in_set(xxx, ancestors)); + *

+ * + * @param dataPermission 数据权限 + * @param currentUser 当前用户 + * @param queryWrapper 查询条件 + */ + private void buildDeptAndChildExpression(DataPermission dataPermission, + DataPermissionCurrentUser currentUser, + QueryWrapper queryWrapper) { + QueryWrapper subQueryWrapper = QueryWrapper.create(); + subQueryWrapper.select(dataPermission.id()).from(dataPermission.deptTableAlias()); + subQueryWrapper.and(qw -> { + qw.eq(dataPermission.id(), currentUser.getDeptId()) + .or("find_in_set(" + currentUser.getDeptId() + ",ancestors)"); + }); + queryWrapper.in(buildColumn(dataPermission.tableAlias(), dataPermission.deptId()), subQueryWrapper); + } + + /** + * 构建 Column + * + * @param tableAlias 表别名 + * @param columnName 字段名称 + * @return 带表别名字段 + */ + private String buildColumn(String tableAlias, String columnName) { + if (StrUtil.isNotEmpty(tableAlias)) { + return "%s.%s".formatted(tableAlias, columnName); + } + return columnName; + } + +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionFilter.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionFilter.java new file mode 100644 index 00000000..e4fb0b19 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataPermissionFilter.java @@ -0,0 +1,40 @@ +/* + * 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.continew.starter.data.mybatis.flex.datapermission; + +/** + * 数据权限过滤器接口 + * + * @author hellokaton + * @since 2.0.2 + */ +public interface DataPermissionFilter { + + /** + * 是否过滤 + * + * @return true:过滤;false:不过滤 + */ + boolean isFilter(); + + /** + * 获取当前用户信息 + * + * @return 当前用户信息 + */ + DataPermissionCurrentUser getCurrentUser(); +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataScope.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataScope.java new file mode 100644 index 00000000..3a137006 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/datapermission/DataScope.java @@ -0,0 +1,51 @@ +/* + * 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.continew.starter.data.mybatis.flex.datapermission; + +/** + * 数据权限枚举 + * + * @author hellokaton + * @since 2.0.2 + */ +public enum DataScope { + + /** + * 全部数据权限 + */ + ALL, + + /** + * 本部门及以下数据权限 + */ + DEPT_AND_CHILD, + + /** + * 本部门数据权限 + */ + DEPT, + + /** + * 仅本人数据权限 + */ + SELF, + + /** + * 自定义数据权限 + */ + CUSTOM, +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/query/QueryWrapperHelper.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/query/QueryWrapperHelper.java new file mode 100644 index 00000000..d286e119 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/query/QueryWrapperHelper.java @@ -0,0 +1,193 @@ +/* + * 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.continew.starter.data.mybatis.flex.query; + +import com.mybatisflex.core.query.QueryWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.text.CharSequenceUtil; +import top.continew.starter.core.exception.BadRequestException; +import top.continew.starter.core.util.ReflectUtils; +import top.continew.starter.core.util.validate.ValidationUtils; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.annotation.QueryIgnore; +import top.continew.starter.data.core.enums.QueryType; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * QueryWrapper 助手 + * + * @author hellokaton + * @author Jasmine + * @since 1.0.0 + */ +public class QueryWrapperHelper { + + private static final Logger log = LoggerFactory.getLogger(QueryWrapperHelper.class); + + private QueryWrapperHelper() { + } + + /** + * 构建 QueryWrapper + * + * @param query 查询条件 + * @param 查询条件数据类型 + * @param 查询数据类型 + * @return QueryWrapper + */ + public static QueryWrapper build(Q query) { + QueryWrapper queryWrapper = QueryWrapper.create(); + // 没有查询条件,直接返回 + 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 查询条件数据类型 + * @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); + if (CollUtil.isNotEmpty(consumers)) { + consumers.forEach(queryWrapper::and); + } + } + return queryWrapper; + } + + /** + * 构建 QueryWrapper Consumer + * + * @param query 查询条件 + * @param field 查询条件字段 + * @param 查询条件数据类型 + * @param 查询数据类型 + * @return QueryWrapper Consumer + */ + private static List> buildWrapperConsumer(Q query, Field field) { + boolean accessible = field.canAccess(query); + try { + field.setAccessible(true); + // 如果字段值为空,直接返回 + Object fieldValue = field.get(query); + if (ObjectUtil.isEmpty(fieldValue)) { + return Collections.emptyList(); + } + // 设置了 @QueryIgnore 注解,直接忽略 + QueryIgnore queryIgnoreAnnotation = field.getAnnotation(QueryIgnore.class); + if (null != queryIgnoreAnnotation && queryIgnoreAnnotation.value()) { + return Collections.emptyList(); + } + // 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名 + String fieldName = field.getName(); + // 没有 @Query 注解,默认等值查询 + Query queryAnnotation = field.getAnnotation(Query.class); + if (null == queryAnnotation) { + return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue)); + } + // 解析单列查询 + QueryType queryType = queryAnnotation.type(); + String[] columns = queryAnnotation.columns(); + final int columnLength = ArrayUtil.length(columns); + List> consumers = new ArrayList<>(columnLength); + if (columnLength <= 1) { + String columnName = columnLength == 1 ? columns[0] : CharSequenceUtil.toUnderlineCase(fieldName); + parse(queryType, columnName, fieldValue, consumers); + return consumers; + } + // 解析多列查询 + for (String column : columns) { + parse(queryType, column, fieldValue, consumers); + } + return consumers; + } catch (BadRequestException 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 Collections.emptyList(); + } + + /** + * 解析查询条件 + * + * @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, (Collection)fieldValue)); + } + case NOT_IN -> { + ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName); + consumers.add(q -> q.notIn(columnName, (Collection)fieldValue)); + } + case IS_NULL -> consumers.add(q -> q.isNull(columnName)); + case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName)); + default -> throw new IllegalArgumentException("暂不支持 [%s] 查询类型".formatted(queryType)); + } + } +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/service/IService.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/service/IService.java new file mode 100644 index 00000000..1643be77 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/service/IService.java @@ -0,0 +1,27 @@ +/* + * 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.continew.starter.data.mybatis.flex.service; + +/** + * 通用业务接口 + * + * @param 实体类型 + * @author hellokaton + * @since 1.2.0 + */ +public interface IService extends com.mybatisflex.core.service.IService { +} diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/service/impl/ServiceImpl.java b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/service/impl/ServiceImpl.java new file mode 100644 index 00000000..0403d61b --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/java/top/continew/starter/data/mybatis/flex/service/impl/ServiceImpl.java @@ -0,0 +1,40 @@ +/* + * 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.continew.starter.data.mybatis.flex.service.impl; + +import top.continew.starter.core.util.ClassUtils; +import top.continew.starter.data.mybatis.flex.base.BaseMapper; +import top.continew.starter.data.mybatis.flex.service.IService; + +/** + * 通用业务实现类 + * + * @param Mapper 接口 + * @param 实体类型 + * @author hellokaton + * @since 1.5.0 + */ +public class ServiceImpl, T> extends com.mybatisflex.spring.service.impl.ServiceImpl implements IService { + + protected final Class[] typeArguments = ClassUtils.getTypeArguments(this.getClass()); + protected final Class entityClass = currentModelClass(); + + protected Class currentModelClass() { + return (Class)this.typeArguments[1]; + } + +} \ No newline at end of file diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..650a0cf7 --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +top.continew.starter.data.mybatis.flex.autoconfigure.MybatisFlexAutoConfiguration \ No newline at end of file diff --git a/continew-starter-data/continew-starter-data-mybatis-flex/src/main/resources/default-data-mybatis-flex.yml b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/resources/default-data-mybatis-flex.yml new file mode 100644 index 00000000..29628c1d --- /dev/null +++ b/continew-starter-data/continew-starter-data-mybatis-flex/src/main/resources/default-data-mybatis-flex.yml @@ -0,0 +1,22 @@ +--- ### MyBatis Flex 配置(https://mybatis-flex.com/zh/base/configuration.html) +mybatis-flex: + # 启动时是否检查 MyBatis XML 文件的存在(默认:false 不检查) + check-config-location: true + ## MyBatis 原生支持配置 + configuration: + # 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名)到经典 Java 属性名 aColumn(驼峰命名)的类似映射 + # 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body,如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名 + map-underscore-to-camel-case: true + # MyBatis 自动映射时未知列或未知属性处理策略,通过该配置可指定 MyBatis 在自动映射过程中遇到未知列或者未知属性时如何处理 + # NONE:不做任何处理 (默认值);WARNING:以日志的形式打印相关警告信息;FAILING:当作映射失败处理,并抛出异常和详细信息 + auto-mapping-unknown-column-behavior: NONE + # 日志配置 + # 默认:org.apache.ibatis.logging.slf4j.Slf4jImpl + # 更详细(会有性能损耗):org.apache.ibatis.logging.stdout.StdOutImpl + # 关闭(可单纯使用 p6spy 分析):org.apache.ibatis.logging.nologging.NoLoggingImpl + log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + key-config: + key-type: generator + # flexId 主键生成器 com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator + value: flexId diff --git a/continew-starter-data/pom.xml b/continew-starter-data/pom.xml index c2987d03..59107d9c 100644 --- a/continew-starter-data/pom.xml +++ b/continew-starter-data/pom.xml @@ -16,6 +16,7 @@ continew-starter-data-core continew-starter-data-mybatis-plus + continew-starter-data-mybatis-flex diff --git a/continew-starter-dependencies/pom.xml b/continew-starter-dependencies/pom.xml index f58824b0..eb95bf9b 100644 --- a/continew-starter-dependencies/pom.xml +++ b/continew-starter-dependencies/pom.xml @@ -47,6 +47,7 @@ 1.38.0 1.16.6 3.5.5 + 1.8.9 4.3.0 3.9.1 2.7.5 @@ -119,6 +120,18 @@ ${mybatis-plus.version} + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + ${mybatis-flex.version} + + + com.mybatis-flex + mybatis-flex-processor + ${mybatis-flex.version} + + com.baomidou @@ -306,6 +319,12 @@ ${revision} + + top.continew + continew-starter-extension-crud-mf + ${revision} + + top.continew @@ -327,6 +346,13 @@ ${revision} + + + top.continew + continew-starter-data-mybatis-flex + ${revision} + + top.continew diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/pom.xml b/continew-starter-extension/continew-starter-extension-crud-mf/pom.xml new file mode 100644 index 00000000..95446bc4 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + top.continew + continew-starter-extension + ${revision} + + + continew-starter-extension-crud-mf + ContiNew Starter 扩展模块 - CRUD(增删改查) + + + + + cn.crane4j + crane4j-spring-boot-starter + + + + org.springframework.data + spring-data-commons + + + + + top.continew + continew-starter-auth-satoken + + + + + top.continew + continew-starter-data-mybatis-flex + + + + + top.continew + continew-starter-file-excel + + + + + top.continew + continew-starter-web + + + \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/CrudRequestMapping.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/CrudRequestMapping.java new file mode 100644 index 00000000..f616dd50 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/CrudRequestMapping.java @@ -0,0 +1,43 @@ +/* + * 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.continew.starter.extension.crud.annotation; + +import top.continew.starter.extension.crud.enums.Api; + +import java.lang.annotation.*; + +/** + * CRUD(增删改查)请求映射器注解 + * + * @author Charles7c + * @since 1.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CrudRequestMapping { + + /** + * 路径映射 URI(等同于:@RequestMapping("/foo1")) + */ + String value() default ""; + + /** + * API 列表 + */ + Api[] api() default {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}; +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/EnableCrudRestController.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/EnableCrudRestController.java new file mode 100644 index 00000000..669ae3b0 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/EnableCrudRestController.java @@ -0,0 +1,35 @@ +/* + * 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.continew.starter.extension.crud.annotation; + +import org.springframework.context.annotation.Import; +import top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration; + +import java.lang.annotation.*; + +/** + * CRUD REST Controller 启用注解 + * + * @author Charles7c + * @since 1.2.0 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import({CrudRestControllerAutoConfiguration.class}) +public @interface EnableCrudRestController { +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/TreeField.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/TreeField.java new file mode 100644 index 00000000..f02b090c --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/annotation/TreeField.java @@ -0,0 +1,74 @@ +/* + * 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.continew.starter.extension.crud.annotation; + +import java.lang.annotation.*; + +/** + * 树结构字段 + * + * @author Charles7c + * @see cn.hutool.core.lang.tree.TreeNodeConfig + * @since 1.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TreeField { + + /** + * ID 字段名 + * + * @return ID 字段名 + */ + String value() default "key"; + + /** + * 父 ID 字段名 + * + * @return 父 ID 字段名 + */ + String parentIdKey() default "parentId"; + + /** + * 名称字段名 + * + * @return 名称字段名 + */ + String nameKey() default "title"; + + /** + * 排序字段名 + * + * @return 排序字段名 + */ + String weightKey() default "sort"; + + /** + * 子列表字段名 + * + * @return 子列表字段名 + */ + String childrenKey() default "children"; + + /** + * 递归深度(< 0 不限制) + * + * @return 递归深度 + */ + int deep() default -1; +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/autoconfigure/CrudRequestMappingHandlerMapping.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/autoconfigure/CrudRequestMappingHandlerMapping.java new file mode 100644 index 00000000..4987183b --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/autoconfigure/CrudRequestMappingHandlerMapping.java @@ -0,0 +1,89 @@ +/* + * 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.continew.starter.extension.crud.autoconfigure; + +import cn.hutool.core.util.ArrayUtil; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.NonNull; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.mvc.condition.RequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.pattern.PathPatternParser; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; + +/** + * CRUD 请求映射器处理器映射器 + * + * @author Charles7c + * @since 1.0.0 + */ +public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMapping { + + @Override + protected RequestMappingInfo getMappingForMethod(@NonNull Method method, @NonNull Class handlerType) { + RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType); + if (null == requestMappingInfo) { + return null; + } + // 如果没有声明 @CrudRequestMapping 注解,直接返回 + if (!handlerType.isAnnotationPresent(CrudRequestMapping.class)) { + return requestMappingInfo; + } + CrudRequestMapping crudRequestMapping = handlerType.getDeclaredAnnotation(CrudRequestMapping.class); + // 过滤 API,如果非本类中定义,且 API 列表中不包含,则忽略 + Api[] apiArr = crudRequestMapping.api(); + Api api = ExceptionUtils.exToNull(() -> Api.valueOf(method.getName().toUpperCase())); + if (method.getDeclaringClass() != handlerType && !ArrayUtil.containsAny(apiArr, Api.ALL, api)) { + return null; + } + // 拼接路径(合并了 @RequestMapping 的部分能力) + return this.getMappingForMethodWrapper(method, handlerType, crudRequestMapping); + } + + private RequestMappingInfo getMappingForMethodWrapper(@NonNull Method method, + @NonNull Class handlerType, + CrudRequestMapping crudRequestMapping) { + RequestMappingInfo info = this.buildRequestMappingInfo(method); + if (null != info) { + RequestMappingInfo typeInfo = this.buildRequestMappingInfo(handlerType); + if (null != typeInfo) { + info = typeInfo.combine(info); + } + String prefix = crudRequestMapping.value(); + if (null != prefix) { + RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); + options.setPatternParser(PathPatternParser.defaultInstance); + info = RequestMappingInfo.paths(prefix).options(options).build().combine(info); + } + } + return info; + } + + private RequestMappingInfo buildRequestMappingInfo(AnnotatedElement element) { + RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); + RequestCondition condition = (element instanceof Class clazz + ? getCustomTypeCondition(clazz) + : getCustomMethodCondition((Method)element)); + return (requestMapping != null ? super.createRequestMappingInfo(requestMapping, condition) : null); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/autoconfigure/CrudRestControllerAutoConfiguration.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/autoconfigure/CrudRestControllerAutoConfiguration.java new file mode 100644 index 00000000..70117f03 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/autoconfigure/CrudRestControllerAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * 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.continew.starter.extension.crud.autoconfigure; + +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.resource.ResourceUrlProvider; + +/** + * CRUD REST Controller 自动配置 + * + * @author Charles7c + * @since 1.0.0 + */ +@Configuration +public class CrudRestControllerAutoConfiguration extends DelegatingWebMvcConfiguration { + + private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class); + + /** + * CRUD 请求映射器处理器映射器(覆盖默认 RequestMappingHandlerMapping) + */ + @Override + public RequestMappingHandlerMapping createRequestMappingHandlerMapping() { + return new CrudRequestMappingHandlerMapping(); + } + + @Bean + @Primary + @Override + public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { + return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider); + } + + @PostConstruct + public void postConstruct() { + log.debug("[ContiNew Starter] - Auto Configuration 'Extension-CRUD REST Controller' completed initialization."); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/constant/ContainerPool.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/constant/ContainerPool.java new file mode 100644 index 00000000..6d311298 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/constant/ContainerPool.java @@ -0,0 +1,34 @@ +/* + * 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.continew.starter.extension.crud.constant; + +/** + * 数据源容器相关常量(Crane4j 数据填充组件使用) + * + * @author Charles7c + * @since 1.2.0 + */ +public class ContainerPool { + + /** + * 用户昵称 + */ + public static final String USER_NICKNAME = "UserNickname"; + + protected ContainerPool() { + } +} \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/controller/BaseController.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/controller/BaseController.java new file mode 100644 index 00000000..bc6e9676 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/controller/BaseController.java @@ -0,0 +1,191 @@ +/* + * 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.continew.starter.extension.crud.controller; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.text.CharSequenceUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.util.ValidateGroup; +import top.continew.starter.extension.crud.enums.Api; +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.req.BaseReq; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseService; +import top.continew.starter.web.model.R; + +import java.util.List; + +/** + * 控制器基类 + * + * @param 业务接口 + * @param 列表类型 + * @param 详情类型 + * @param 查询条件 + * @param 创建或修改类型 + * @author Charles7c + * @since 1.0.0 + */ +public abstract class BaseController, L, D, Q, C extends BaseReq> { + + @Autowired + protected S baseService; + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页信息 + */ + @Operation(summary = "分页查询列表", description = "分页查询列表") + @ResponseBody + @GetMapping + public R> page(Q query, @Validated PageQuery pageQuery) { + this.checkPermission(Api.LIST); + return R.ok(baseService.page(query, pageQuery)); + } + + /** + * 查询树列表 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @return 树列表信息 + */ + @Operation(summary = "查询树列表", description = "查询树列表") + @ResponseBody + @GetMapping("/tree") + public R>> tree(Q query, SortQuery sortQuery) { + this.checkPermission(Api.LIST); + return R.ok(baseService.tree(query, sortQuery, false)); + } + + /** + * 查询列表 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @return 列表信息 + */ + @Operation(summary = "查询列表", description = "查询列表") + @ResponseBody + @GetMapping("/list") + public R> list(Q query, SortQuery sortQuery) { + this.checkPermission(Api.LIST); + return R.ok(baseService.list(query, sortQuery)); + } + + /** + * 查询详情 + * + * @param id ID + * @return 详情信息 + */ + @Operation(summary = "查询详情", description = "查询详情") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @ResponseBody + @GetMapping("/{id}") + public R get(@PathVariable Long id) { + this.checkPermission(Api.LIST); + return R.ok(baseService.get(id)); + } + + /** + * 新增 + * + * @param req 创建信息 + * @return 自增 ID + */ + @Operation(summary = "新增数据", description = "新增数据") + @ResponseBody + @PostMapping + public R add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) { + this.checkPermission(Api.ADD); + return R.ok("新增成功", baseService.add(req)); + } + + /** + * 修改 + * + * @param req 修改信息 + * @param id ID + * @return / + */ + @Operation(summary = "修改数据", description = "修改数据") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @ResponseBody + @PutMapping("/{id}") + public R update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) { + this.checkPermission(Api.UPDATE); + baseService.update(req, id); + return R.ok("修改成功"); + } + + /** + * 删除 + * + * @param ids ID 列表 + * @return / + */ + @Operation(summary = "删除数据", description = "删除数据") + @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) + @ResponseBody + @DeleteMapping("/{ids}") + public R delete(@PathVariable List ids) { + this.checkPermission(Api.DELETE); + baseService.delete(ids); + return R.ok("删除成功"); + } + + /** + * 导出 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @param response 响应对象 + */ + @Operation(summary = "导出数据", description = "导出数据") + @GetMapping("/export") + public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { + this.checkPermission(Api.EXPORT); + baseService.export(query, sortQuery, response); + } + + /** + * 根据 API 类型进行权限验证 + * + * @param api API 类型 + */ + protected void checkPermission(Api api) { + CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class); + String path = crudRequestMapping.value(); + String permissionPrefix = String.join(StringConstants.COLON, CharSequenceUtil + .splitTrim(path, StringConstants.SLASH)); + StpUtil.checkPermission("%s:%s".formatted(permissionPrefix, api.name().toLowerCase())); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/converter/ExcelBaseEnumConverter.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/converter/ExcelBaseEnumConverter.java new file mode 100644 index 00000000..37a77c7a --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/converter/ExcelBaseEnumConverter.java @@ -0,0 +1,91 @@ +/* + * 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.continew.starter.extension.crud.converter; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ClassUtil; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.data.mybatis.flex.base.IBaseEnum; + +/** + * Easy Excel 枚举接口转换器 + * + * @see IBaseEnum + * @author Charles7c + * @since 1.2.0 + */ +public class ExcelBaseEnumConverter implements Converter> { + + @Override + public Class supportJavaTypeKey() { + return IBaseEnum.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 转换为 Java 数据(读取 Excel) + */ + @Override + public IBaseEnum convertToJavaData(ReadCellData cellData, + ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return this.getEnum(IBaseEnum.class, Convert.toStr(cellData.getData())); + } + + /** + * 转换为 Excel 数据(写入 Excel) + */ + @Override + public WriteCellData convertToExcelData(IBaseEnum value, + ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (null == value) { + return new WriteCellData<>(StringConstants.EMPTY); + } + return new WriteCellData<>(value.getDescription()); + } + + /** + * 通过 value 获取枚举对象,获取不到时为 {@code null} + * + * @param enumType 枚举类型 + * @param description 描述 + * @return 对应枚举 ,获取不到时为 {@code null} + */ + private IBaseEnum getEnum(Class enumType, String description) { + Object[] enumConstants = enumType.getEnumConstants(); + for (Object enumConstant : enumConstants) { + if (ClassUtil.isAssignable(IBaseEnum.class, enumType)) { + IBaseEnum baseEnum = (IBaseEnum)enumConstant; + if (baseEnum.getDescription().equals(description)) { + return baseEnum; + } + } + } + return null; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/enums/Api.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/enums/Api.java new file mode 100644 index 00000000..abbcec7c --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/enums/Api.java @@ -0,0 +1,63 @@ +/* + * 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.continew.starter.extension.crud.enums; + +/** + * API 类型枚举 + * + * @author Charles7c + * @since 1.0.0 + */ +public enum Api { + + /** + * 所有 API + */ + ALL, + /** + * 分页 + */ + PAGE, + /** + * 树列表 + */ + TREE, + /** + * 列表 + */ + LIST, + /** + * 详情 + */ + GET, + /** + * 新增 + */ + ADD, + /** + * 修改 + */ + UPDATE, + /** + * 删除 + */ + DELETE, + /** + * 导出 + */ + EXPORT, +} \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseCreateDO.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseCreateDO.java new file mode 100644 index 00000000..eef1af83 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseCreateDO.java @@ -0,0 +1,62 @@ +/* + * 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.continew.starter.extension.crud.model.entity; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 实体类基类 + * + *

+ * 通用字段:创建人、创建时间 + *

+ * + * @author Charles7c + * @since 2.0.1 + */ +public class BaseCreateDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 创建人 + */ + private Long createUser; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + public Long getCreateUser() { + return createUser; + } + + public void setCreateUser(Long createUser) { + this.createUser = createUser; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseDO.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseDO.java new file mode 100644 index 00000000..5b62bd58 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseDO.java @@ -0,0 +1,84 @@ +/* + * 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.continew.starter.extension.crud.model.entity; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 实体类基类 + * + * @author Charles7c + * @since 1.0.0 + */ +public class BaseDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 创建人 + */ + private Long createUser; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 修改人 + */ + private Long updateUser; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + public Long getCreateUser() { + return createUser; + } + + public void setCreateUser(Long createUser) { + this.createUser = createUser; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public Long getUpdateUser() { + return updateUser; + } + + public void setUpdateUser(Long updateUser) { + this.updateUser = updateUser; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseIdDO.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseIdDO.java new file mode 100644 index 00000000..d7746fa2 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseIdDO.java @@ -0,0 +1,52 @@ +/* + * 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.continew.starter.extension.crud.model.entity; + +import com.mybatisflex.annotation.Id; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 实体类基类 + * + *

+ * 通用字段:ID 主键 + *

+ * + * @author Charles7c + * @since 2.0.1 + */ +public class BaseIdDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseUpdateDO.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseUpdateDO.java new file mode 100644 index 00000000..c9c2e46a --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/entity/BaseUpdateDO.java @@ -0,0 +1,62 @@ +/* + * 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.continew.starter.extension.crud.model.entity; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 实体类基类 + * + *

+ * 通用字段:创建人、创建时间 + *

+ * + * @author Charles7c + * @since 2.0.1 + */ +public class BaseUpdateDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 修改人 + */ + private Long updateUser; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + public Long getUpdateUser() { + return updateUser; + } + + public void setUpdateUser(Long updateUser) { + this.updateUser = updateUser; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/query/PageQuery.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/query/PageQuery.java new file mode 100644 index 00000000..6b5ab1c4 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/query/PageQuery.java @@ -0,0 +1,76 @@ +/* + * 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.continew.starter.extension.crud.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import org.hibernate.validator.constraints.Range; +import org.springdoc.core.annotations.ParameterObject; + +import java.io.Serial; + +/** + * 分页查询条件 + * + * @author Charles7c + * @since 1.0.0 + */ +@ParameterObject +@Schema(description = "分页查询条件") +public class PageQuery extends SortQuery { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 默认页码:1 + */ + private static final int DEFAULT_PAGE = 1; + /** + * 默认每页条数:10 + */ + private static final int DEFAULT_SIZE = 10; + + /** + * 页码 + */ + @Schema(description = "页码", example = "1") + @Min(value = 1, message = "页码最小值为 {value}") + private Integer page = DEFAULT_PAGE; + + /** + * 每页条数 + */ + @Schema(description = "每页条数", example = "10") + @Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})") + private Integer size = DEFAULT_SIZE; + + public Integer getPage() { + return page; + } + + public void setPage(Integer page) { + this.page = page; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/query/SortQuery.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/query/SortQuery.java new file mode 100644 index 00000000..d6c7229b --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/query/SortQuery.java @@ -0,0 +1,78 @@ +/* + * 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.continew.starter.extension.crud.model.query; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.text.CharSequenceUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.data.domain.Sort; +import top.continew.starter.core.constant.StringConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 排序查询条件 + * + * @author Charles7c + * @since 1.0.0 + */ +@Schema(description = "排序查询条件") +public class SortQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 排序条件 + */ + @Schema(description = "排序条件", example = "createTime,desc") + private String[] sort; + + /** + * 解析排序条件为 Spring 分页排序实体 + * + * @return Spring 分页排序实体 + */ + public Sort getSort() { + if (ArrayUtil.isEmpty(sort)) { + return Sort.unsorted(); + } + + List orders = new ArrayList<>(sort.length); + if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) { + // e.g "sort=createTime,desc&sort=name,asc" + for (String s : sort) { + List sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA); + Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList + .get(0)); + orders.add(order); + } + } else { + // e.g "sort=createTime,desc" + Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]); + orders.add(order); + } + return Sort.by(orders); + } + + public void setSort(String[] sort) { + this.sort = sort; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/req/BaseReq.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/req/BaseReq.java new file mode 100644 index 00000000..7e334a9a --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/req/BaseReq.java @@ -0,0 +1,32 @@ +/* + * 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.continew.starter.extension.crud.model.req; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 请求基类 + * + * @author Charles7c + * @since 1.0.0 + */ +public class BaseReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/BaseDetailResp.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/BaseDetailResp.java new file mode 100644 index 00000000..dd58dcb7 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/BaseDetailResp.java @@ -0,0 +1,86 @@ +/* + * 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.continew.starter.extension.crud.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.Mapping; +import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import top.continew.starter.extension.crud.constant.ContainerPool; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 详情响应基类 + * + * @author Charles7c + * @since 1.0.0 + */ +public class BaseDetailResp extends BaseResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 修改人 + */ + @JsonIgnore + @ConditionOnPropertyNotNull + @Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "updateUserString")) + private Long updateUser; + + /** + * 修改人 + */ + @Schema(description = "修改人", example = "李四") + @ExcelProperty(value = "修改人", order = Integer.MAX_VALUE - 2) + private String updateUserString; + + /** + * 修改时间 + */ + @Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "修改时间", order = Integer.MAX_VALUE - 1) + private LocalDateTime updateTime; + + public Long getUpdateUser() { + return updateUser; + } + + public void setUpdateUser(Long updateUser) { + this.updateUser = updateUser; + } + + public String getUpdateUserString() { + return updateUserString; + } + + public void setUpdateUserString(String updateUserString) { + this.updateUserString = updateUserString; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/BaseResp.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/BaseResp.java new file mode 100644 index 00000000..458ead46 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/BaseResp.java @@ -0,0 +1,116 @@ +/* + * 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.continew.starter.extension.crud.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.Mapping; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import top.continew.starter.extension.crud.constant.ContainerPool; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 响应基类 + * + * @author Charles7c + * @since 1.0.0 + */ +public class BaseResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + @ExcelProperty(value = "ID", order = 1) + private Long id; + + /** + * 创建人 + */ + @JsonIgnore + @Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "createUserString")) + private Long createUser; + + /** + * 创建人 + */ + @Schema(description = "创建人", example = "超级管理员") + @ExcelProperty(value = "创建人", order = Integer.MAX_VALUE - 4) + private String createUserString; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "创建时间", order = Integer.MAX_VALUE - 3) + private LocalDateTime createTime; + + /** + * 是否禁用修改 + */ + @Schema(description = "是否禁用修改", example = "true") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean disabled; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCreateUser() { + return createUser; + } + + public void setCreateUser(Long createUser) { + this.createUser = createUser; + } + + public String getCreateUserString() { + return createUserString; + } + + public void setCreateUserString(String createUserString) { + this.createUserString = createUserString; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public Boolean getDisabled() { + return disabled; + } + + public void setDisabled(Boolean disabled) { + this.disabled = disabled; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/PageResp.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/PageResp.java new file mode 100644 index 00000000..ece21115 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/model/resp/PageResp.java @@ -0,0 +1,146 @@ +/* + * 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.continew.starter.extension.crud.model.resp; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import com.mybatisflex.core.paginate.Page; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 分页信息 + * + * @param 列表数据类型 + * @author Charles7c + * @since 1.0.0 + */ +@Schema(description = "分页信息") +public class PageResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 列表数据 + */ + @Schema(description = "列表数据") + private List list; + + /** + * 总记录数 + */ + @Schema(description = "总记录数", example = "10") + private long total; + + /** + * 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据 + * + * @param page MyBatis Plus 分页数据 + * @param targetClass 目标类型 Class 对象 + * @param 源列表数据类型 + * @param 目标列表数据类型 + * @return 分页信息 + */ + public static PageResp build(Page page, Class targetClass) { + if (null == page) { + return empty(); + } + PageResp pageResp = new PageResp<>(); + pageResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass)); + pageResp.setTotal(page.getTotalRow()); + return pageResp; + } + + /** + * 基于 MyBatis Plus 分页数据构建分页信息 + * + * @param page MyBatis Plus 分页数据 + * @param 列表数据类型 + * @return 分页信息 + */ + public static PageResp build(Page page) { + if (null == page) { + return empty(); + } + PageResp pageResp = new PageResp<>(); + pageResp.setList(page.getRecords()); + pageResp.setTotal(page.getTotalRow()); + return pageResp; + } + + /** + * 基于列表数据构建分页信息 + * + * @param page 页码 + * @param size 每页条数 + * @param list 列表数据 + * @param 列表数据类型 + * @return 分页信息 + */ + public static PageResp build(int page, int size, List list) { + if (CollUtil.isEmpty(list)) { + return empty(); + } + PageResp pageResp = new PageResp<>(); + pageResp.setTotal(list.size()); + // 对列表数据进行分页 + int fromIndex = (page - 1) * size; + int toIndex = page * size + fromIndex; + if (fromIndex > list.size()) { + pageResp.setList(new ArrayList<>(0)); + } else if (toIndex >= list.size()) { + pageResp.setList(list.subList(fromIndex, list.size())); + } else { + pageResp.setList(list.subList(fromIndex, toIndex)); + } + return pageResp; + } + + /** + * 空分页信息 + * + * @param 列表数据类型 + * @return 分页信息 + */ + private static PageResp empty() { + PageResp pageResp = new PageResp<>(); + pageResp.setList(Collections.emptyList()); + return pageResp; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/BaseService.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/BaseService.java new file mode 100644 index 00000000..d56e1ac2 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/BaseService.java @@ -0,0 +1,106 @@ +/* + * 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.continew.starter.extension.crud.service; + +import cn.hutool.core.lang.tree.Tree; +import jakarta.servlet.http.HttpServletResponse; +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 java.util.List; + +/** + * 业务接口基类 + * + * @param 列表类型 + * @param 详情类型 + * @param 查询条件 + * @param 创建或修改类型 + * @author Charles7c + * @since 1.0.0 + */ +public interface BaseService { + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp page(Q query, PageQuery pageQuery); + + /** + * 查询树列表 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段) + * @return 树列表信息 + */ + List> tree(Q query, SortQuery sortQuery, boolean isSimple); + + /** + * 查询列表 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @return 列表信息 + */ + List list(Q query, SortQuery sortQuery); + + /** + * 查看详情 + * + * @param id ID + * @return 详情信息 + */ + D get(Long id); + + /** + * 新增 + * + * @param req 创建信息 + * @return 自增 ID + */ + Long add(C req); + + /** + * 修改 + * + * @param req 修改信息 + * @param id ID + */ + void update(C req, Long id); + + /** + * 删除 + * + * @param ids ID 列表 + */ + void delete(List ids); + + /** + * 导出 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @param response 响应对象 + */ + void export(Q query, SortQuery sortQuery, HttpServletResponse response); +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/CommonUserService.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/CommonUserService.java new file mode 100644 index 00000000..72f6b010 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/CommonUserService.java @@ -0,0 +1,39 @@ +/* + * 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.continew.starter.extension.crud.service; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import top.continew.starter.extension.crud.constant.ContainerPool; + +/** + * 公共用户业务接口 + * + * @author Charles7c + * @since 1.0.0 + */ +public interface CommonUserService { + + /** + * 根据 ID 查询昵称 + * + * @param id ID + * @return 昵称 + */ + @ContainerMethod(namespace = ContainerPool.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS) + String getNicknameById(Long id); +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/impl/BaseServiceImpl.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/impl/BaseServiceImpl.java new file mode 100644 index 00000000..474c9ed6 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/impl/BaseServiceImpl.java @@ -0,0 +1,319 @@ +/* + * 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.continew.starter.extension.crud.service.impl; + +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.lang.Opt; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +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.springframework.data.domain.Sort; +import org.springframework.transaction.annotation.Transactional; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.ReflectUtils; +import top.continew.starter.core.util.validate.ValidationUtils; +import top.continew.starter.data.mybatis.flex.base.BaseMapper; +import top.continew.starter.data.mybatis.flex.query.QueryWrapperHelper; +import top.continew.starter.data.mybatis.flex.service.impl.ServiceImpl; +import top.continew.starter.extension.crud.annotation.TreeField; +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; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * 业务实现基类 + * + * @param Mapper 接口 + * @param 实体类型 + * @param 列表类型 + * @param 详情类型 + * @param 查询条件 + * @param 创建或修改类型 + * @author Charles7c + * @since 1.0.0 + */ +public abstract class BaseServiceImpl, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl implements BaseService { + + protected final Class listClass = this.currentListClass(); + protected final Class detailClass = this.currentDetailClass(); + protected final Class queryClass = this.currentQueryClass(); + private final List queryFields = ReflectUtils.getNonStaticFields(this.queryClass); + + @Override + public PageResp page(Q query, PageQuery pageQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + Page page = mapper.paginate(pageQuery.getPage(), pageQuery.getSize(), queryWrapper); + PageResp pageResp = PageResp.build(page, listClass); + pageResp.getList().forEach(this::fill); + return pageResp; + } + + @Override + public List> tree(Q query, SortQuery sortQuery, boolean isSimple) { + List list = this.list(query, sortQuery); + if (CollUtil.isEmpty(list)) { + return new ArrayList<>(0); + } + // 如果构建简单树结构,则不包含基本树结构之外的扩展字段 + TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG; + TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); + if (!isSimple) { + // 根据 @TreeField 配置生成树结构配置 + treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField); + } + // 构建树 + return TreeUtils.build(list, 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 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 + .getName())))); + } + }); + } + + @Override + public List list(Q query, SortQuery sortQuery) { + List list = this.list(query, sortQuery, listClass); + list.forEach(this::fill); + return list; + } + + @Override + public D get(Long id) { + T entity = super.getById(id); + D detail = BeanUtil.toBean(entity, detailClass); + this.fill(detail); + return detail; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long add(C req) { + this.beforeAdd(req); + T entity = BeanUtil.copyProperties(req, this.entityClass); + mapper.insert(entity); + this.afterAdd(req, entity); + return entity.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(C req, Long id) { + this.beforeUpdate(req, id); + T entity = this.getById(id); + BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue()); + mapper.update(entity); + this.afterUpdate(req, entity); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(List ids) { + this.beforeDelete(ids); + mapper.deleteBatchByIds(ids); + this.afterDelete(ids); + } + + @Override + public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { + List list = this.list(query, sortQuery, detailClass); + list.forEach(this::fill); + ExcelUtils.export(list, "导出数据", detailClass, response); + } + + /** + * 查询列表 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @param targetClass 指定类型 + * @return 列表信息 + */ + protected List list(Q query, SortQuery sortQuery, Class targetClass) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + // 设置排序 + this.sort(queryWrapper, sortQuery); + List entityList = mapper.selectListByQuery(queryWrapper); + if (this.entityClass == targetClass) { + return (List)entityList; + } + return BeanUtil.copyToList(entityList, targetClass); + } + + /** + * 设置排序 + * + * @param queryWrapper 查询条件封装对象 + * @param sortQuery 排序查询条件 + */ + protected void sort(QueryWrapper queryWrapper, SortQuery sortQuery) { + Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort(); + List entityFields = ReflectUtils.getNonStaticFields(this.entityClass); + for (Sort.Order order : sort) { + if (null == order) { + continue; + } + String property = order.getProperty(); + String checkProperty; + // 携带表别名则获取 . 后面的字段名 + if (property.contains(StringConstants.DOT)) { + checkProperty = CollUtil.getLast(CharSequenceUtil.split(property, StringConstants.DOT)); + } else { + checkProperty = property; + } + Optional optional = entityFields.stream() + .filter(field -> checkProperty.equals(field.getName())) + .findFirst(); + ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property); + queryWrapper.orderBy(CharSequenceUtil.toUnderlineCase(property), order.isAscending()); + } + } + + /** + * 填充数据 + * + * @param obj 待填充信息 + */ + protected void fill(Object obj) { + if (null == obj) { + return; + } + OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class); + operateTemplate.execute(obj); + } + + /** + * 构建 QueryWrapper + * + * @param query 查询条件 + * @return QueryWrapper + */ + protected QueryWrapper buildQueryWrapper(Q query) { + QueryWrapper queryWrapper = QueryWrapper.create(); + // 解析并拼接查询条件 + return QueryWrapperHelper.build(query, queryFields, queryWrapper); + } + + /** + * 新增前置处理 + * + * @param req 创建信息 + */ + protected void beforeAdd(C req) { + /* 新增前置处理 */ + } + + /** + * 修改前置处理 + * + * @param req 修改信息 + * @param id ID + */ + protected void beforeUpdate(C req, Long id) { + /* 修改前置处理 */ + } + + /** + * 删除前置处理 + * + * @param ids ID 列表 + */ + protected void beforeDelete(List ids) { + /* 删除前置处理 */ + } + + /** + * 新增后置处理 + * + * @param req 创建信息 + * @param entity 实体信息 + */ + protected void afterAdd(C req, T entity) { + /* 新增后置处理 */ + } + + /** + * 修改后置处理 + * + * @param req 修改信息 + * @param entity 实体信息 + */ + protected void afterUpdate(C req, T entity) { + /* 修改后置处理 */ + } + + /** + * 删除后置处理 + * + * @param ids ID 列表 + */ + protected void afterDelete(List ids) { + /* 删除后置处理 */ + } + + /** + * 获取当前列表信息类型 + * + * @return 当前列表信息类型 + */ + protected Class currentListClass() { + return (Class)this.typeArguments[2]; + } + + /** + * 获取当前详情信息类型 + * + * @return 当前详情信息类型 + */ + protected Class currentDetailClass() { + return (Class)this.typeArguments[3]; + } + + /** + * 获取当前查询条件类型 + * + * @return 当前查询条件类型 + */ + protected Class currentQueryClass() { + return (Class)this.typeArguments[4]; + } + +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/util/TreeUtils.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/util/TreeUtils.java new file mode 100644 index 00000000..8db19fab --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/util/TreeUtils.java @@ -0,0 +1,95 @@ +/* + * 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.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 转换的实体 为数据源里的对象类型 + * @param ID类型 + * @param list 源数据集合 + * @param nodeParser 转换器 + * @return List 树列表 + */ + public static List> build(List list, NodeParser nodeParser) { + return build(list, DEFAULT_CONFIG, nodeParser); + } + + /** + * 树构建 + * + * @param 转换的实体 为数据源里的对象类型 + * @param ID类型 + * @param list 源数据集合 + * @param treeNodeConfig 配置 + * @param nodeParser 转换器 + * @return List 树列表 + */ + public static List> build(List list, TreeNodeConfig treeNodeConfig, NodeParser 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()); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/util/ValidateGroup.java b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/util/ValidateGroup.java new file mode 100644 index 00000000..9cd85d33 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/util/ValidateGroup.java @@ -0,0 +1,45 @@ +/* + * 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.continew.starter.extension.crud.util; + +import jakarta.validation.groups.Default; + +/** + * 分组校验 + * + * @author Charles7c + * @since 1.0.0 + */ +public interface ValidateGroup extends Default { + + /** + * 分组校验-增删改查 + */ + interface Crud extends ValidateGroup { + /** + * 分组校验-创建 + */ + interface Add extends Crud { + } + + /** + * 分组校验-修改 + */ + interface Update extends Crud { + } + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud-mf/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-extension/continew-starter-extension-crud-mf/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..e69de29b diff --git a/continew-starter-extension/pom.xml b/continew-starter-extension/pom.xml index 18b788c4..c0b58e18 100644 --- a/continew-starter-extension/pom.xml +++ b/continew-starter-extension/pom.xml @@ -15,6 +15,7 @@ continew-starter-extension-crud + continew-starter-extension-crud-mf