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